createRoot 可让你创建一个 root,以在浏览器 DOM 节点内显示 React 组件。
const root = createRoot(domNode, options?)参考
createRoot(domNode, options?)
调用 createRoot 可创建一个 React root,用于在浏览器 DOM 元素内显示内容。
import { createRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = createRoot(domNode);React 会为 domNode 创建一个 root,并接管其内部 DOM 的管理。创建 root 后,你需要调用 root.render 才能在其中显示 React 组件:
root.render(<App />);一个完全使用 React 构建的应用通常只需要为其根组件调用一次 createRoot。如果页面只是在某些部分零散地使用 React,则可以根据需要创建任意多个独立 root。
参数
-
domNode:一个 DOM 元素。 React 会为这个 DOM 元素创建一个 root,并允许你在该 root 上调用函数,例如用render显示渲染后的 React 内容。 -
可选
options:一个包含此 React root 选项的对象。- 可选
onCaughtError:当 React 在错误边界中捕获到错误时调用的回调。调用时会传入被错误边界捕获的error,以及一个包含componentStack的errorInfo对象。 - 可选
onUncaughtError:当抛出错误但未被错误边界捕获时调用的回调。调用时会传入抛出的error,以及一个包含componentStack的errorInfo对象。 - 可选
onRecoverableError:当 React 自动从错误中恢复时调用的回调。调用时会传入 React 抛出的error,以及一个包含componentStack的errorInfo对象。某些可恢复错误可能会在error.cause中包含原始错误原因。 - 可选
identifierPrefix:React 用于由useId生成的 ID 的字符串前缀。在同一页面使用多个 root 时,这有助于避免冲突。
- 可选
返回值
createRoot 返回一个包含两个方法的对象:render 和 unmount.
注意事项
- 如果你的应用是服务端渲染的,则不支持使用
createRoot()。请改用hydrateRoot()。 - 你的应用中大概率只需要调用一次
createRoot。如果你使用框架,它可能已经替你完成了这次调用。 - 当你想要在 DOM 树中某个不是你组件子节点的不同位置渲染一段 JSX 时(例如模态框或提示框),请使用
createPortal而不是createRoot。
root.render(reactNode)
调用 root.render 可将一段 JSX(“React 节点”)显示到 React root 的浏览器 DOM 节点中。
root.render(<App />);React 会在 root 中显示 <App />,并接管其内部 DOM 的管理。
参数
reactNode:你想要显示的 React 节点。这通常是一段 JSX,例如<App />,但你也可以传入由createElement()构造的 React 元素、字符串、数字、null或undefined。
返回值
root.render 返回 undefined。
注意事项
-
你第一次调用
root.render时,React 会在将 React 组件渲染进去之前清空 React root 内现有的所有 HTML 内容。 -
如果你的 root 的 DOM 节点中包含由 React 在服务端或构建期间生成的 HTML,请改用
hydrateRoot(),它会将事件处理函数附加到现有 HTML 上。 -
如果你在同一个 root 上多次调用
render,React 会根据需要更新 DOM,以反映你传入的最新 JSX。React 会通过与之前渲染的树进行”匹配”来决定哪些 DOM 部分可以复用,哪些需要重新创建。在同一个 root 上再次调用render,类似于在 root 组件上调用set函数:React 会避免不必要的 DOM 更新。 -
尽管渲染一旦开始就是同步的,但
root.render(...)本身并不是同步的。这意味着在root.render()之后的代码,可能会在此次渲染的任何效果(useLayoutEffect、useEffect)执行之前运行。这通常没有问题,也很少需要调整。在极少数效果时序很重要的情况下,你可以将root.render(...)包裹在flushSync中,以确保初始渲染完全同步执行。const root = createRoot(document.getElementById('root'));root.render(<App />);// 🚩 HTML 目前还不会包含已渲染的 <App />:console.log(document.body.innerHTML);
root.unmount()
调用 root.unmount 可销毁 React root 内已渲染的树。
root.unmount();一个完全使用 React 构建的应用通常不会调用 root.unmount。
当你的 React root 的 DOM 节点(或其任意祖先节点)可能会被其他代码从 DOM 中移除时,这个方法尤其有用。例如,设想一个 jQuery 标签页面板会把未激活的标签页从 DOM 中移除。如果某个标签页被移除,其中的一切内容(包括内部的 React root)也会一并从 DOM 中移除。在这种情况下,你需要通过调用 root.unmount 告诉 React “停止”管理被移除的 root 内容。否则,被移除 root 中的组件不会知道需要清理并释放全局资源,比如订阅。
调用 root.unmount 会卸载 root 中所有组件,并将 React 从 root DOM 节点上“分离”,包括移除树中的任何事件处理函数或状态。
参数
root.unmount 不接受任何参数。
返回值
root.unmount 返回 undefined。
注意事项
-
调用
root.unmount会卸载树中的所有组件,并将 React 从 root DOM 节点上“分离”。 -
一旦调用了
root.unmount,你就不能再在同一个 root 上调用root.render。尝试在已卸载的 root 上调用root.render会抛出 “Cannot update an unmounted root” 错误。不过,在某个 DOM 节点上的之前的 root 被卸载后,你可以为同一个 DOM 节点创建一个新的 root。
用法
渲染一个完全使用 React 构建的应用
如果你的应用完全使用 React 构建,请为整个应用创建一个单独的 root。
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);通常,你只需要在启动时运行一次这段代码。它会:
- 找到你 HTML 中定义的 浏览器 DOM 节点。
- 在其中显示应用的 React 组件。
import { createRoot } from 'react-dom/client'; import App from './App.js'; import './styles.css'; const root = createRoot(document.getElementById('root')); root.render(<App />);
如果你的应用完全使用 React 构建,你通常不需要再创建更多 root,也不需要再次调用 root.render。
从现在起,React 将管理你整个应用的 DOM。要添加更多组件,请将它们嵌套在 App 组件内部。 当你需要更新 UI 时,每个组件都可以通过使用 state。 来完成。当你需要在 DOM 节点外显示诸如模态框或提示框之类的额外内容时,请使用 portal 渲染。
渲染一个部分使用 React 构建的页面
如果你的页面不是完全使用 React 构建的,你可以多次调用 createRoot,为由 React 管理的每个顶层 UI 部分创建一个 root。你可以通过调用 root.render 在每个 root 中显示不同的内容。
这里,两个不同的 React 组件被渲染到 index.html 文件中定义的两个 DOM 节点里:
import './styles.css'; import { createRoot } from 'react-dom/client'; import { Comments, Navigation } from './Components.js'; const navDomNode = document.getElementById('navigation'); const navRoot = createRoot(navDomNode); navRoot.render(<Navigation />); const commentDomNode = document.getElementById('comments'); const commentRoot = createRoot(commentDomNode); commentRoot.render(<Comments />);
你也可以使用 document.createElement() 创建一个新的 DOM 节点,然后手动将其添加到文档中。
const domNode = document.createElement('div');
const root = createRoot(domNode);
root.render(<Comment />);
document.body.appendChild(domNode); // 你可以把它添加到文档中的任何位置要将 React 树从 DOM 节点中移除并清理其使用的所有资源,请调用 root.unmount.
root.unmount();如果你的 React 组件位于用其他框架编写的应用中,这种方式尤其有用。
更新 root 组件
你可以在同一个 root 上多次调用 render。只要组件树结构与之前渲染的内容相匹配,React 就会保留状态。 注意你可以在输入框中输入内容,这意味着本例中每秒重复调用 render 所产生的更新并不会破坏现有内容:
import { createRoot } from 'react-dom/client'; import './styles.css'; import App from './App.js'; const root = createRoot(document.getElementById('root')); let i = 0; setInterval(() => { root.render(<App counter={i} />); i++; }, 1000);
多次调用 render 并不常见。通常,你的组件会改为更新 state。
生产环境中的错误日志记录
默认情况下,React 会将所有错误记录到控制台。要实现你自己的错误上报,你可以提供可选的错误处理 root 选项 onUncaughtError、onCaughtError 和 onRecoverableError:
import { createRoot } from "react-dom/client";
import { reportCaughtError } from "./reportError";
const container = document.getElementById("root");
const root = createRoot(container, {
onCaughtError: (error, errorInfo) => {
if (error.message !== "Known error") {
reportCaughtError({
error,
componentStack: errorInfo.componentStack,
});
}
},
});onCaughtError 选项是一个接收两个参数的函数:
- error,即被抛出的错误。
- errorInfo 对象,其中包含该错误的 componentStack。
结合 onUncaughtError 和 onRecoverableError,你可以实现自己的错误上报系统:
import { createRoot } from "react-dom/client"; import App from "./App.js"; import { onCaughtErrorProd, onRecoverableErrorProd, onUncaughtErrorProd, } from "./reportError"; const container = document.getElementById("root"); const root = createRoot(container, { // 请记住在开发环境中移除这些选项,以便使用 // React 的默认处理程序,或者为开发环境实现你自己的覆盖层。 // 这些处理程序仅在此处无条件指定用于演示目的。 onCaughtError: onCaughtErrorProd, onRecoverableError: onRecoverableErrorProd, onUncaughtError: onUncaughtErrorProd, }); root.render(<App />);
故障排查
我已经创建了一个 root,但没有显示任何内容
请确保你没有忘记把你的应用实际 渲染 到 root 中:
import { createRoot } from 'react-dom/client';
import App from './App.js';
const root = createRoot(document.getElementById('root'));
root.render(<App />);在你这样做之前,不会显示任何内容。
我遇到一个错误:“You passed a second argument to root.render”
一个常见错误是把 createRoot 的选项传给 root.render(...):
要修复这个问题,请把 root 选项传给 createRoot(...),而不是 root.render(...):
// 🚩 错误:root.render 只接受一个参数。
root.render(App, {onUncaughtError});
// ✅ 正确:将选项传给 createRoot。
const root = createRoot(container, {onUncaughtError});
root.render(<App />);我遇到一个错误:“Target container is not a DOM element”
这个错误意味着你传给 createRoot 的内容不是一个 DOM 节点。
如果你不确定发生了什么,试着把它打印出来:
const domNode = document.getElementById('root');
console.log(domNode); // ???
const root = createRoot(domNode);
root.render(<App />);例如,如果 domNode 是 null,这意味着在你调用时,getElementById 返回了 null。如果在你调用时,文档中还没有带有给定 ID 的节点,就会发生这种情况。可能有以下几个原因:
- 你要查找的 ID 可能与你在 HTML 文件中使用的 ID 不同。检查是否有拼写错误!
- 你构建产物中的
<script>标签无法“看到” HTML 中出现在它 之后 的任何 DOM 节点。
另一种常见的导致此错误的方式,是写成 createRoot(<App />),而不是 createRoot(domNode)。
我遇到一个错误:“Functions are not valid as a React child.”
这个错误意味着你传给 root.render 的内容不是一个 React 组件。
如果你使用 Component 而不是 <Component /> 调用 root.render,就可能发生这种情况:
// 🚩 错误:App 是一个函数,不是一个组件。
root.render(App);
// ✅ 正确:<App /> 是一个组件。
root.render(<App />);或者,如果你传给 root.render 的是一个函数,而不是调用它后的结果:
// 🚩 错误:createApp 是一个函数,不是一个组件。
root.render(createApp);
// ✅ 正确:调用 createApp 以返回一个组件。
root.render(createApp());我的服务端渲染 HTML 被从头重新创建了
如果你的应用是服务端渲染的,并且包含 React 生成的初始 HTML,你可能会注意到,创建 root 并调用 root.render 会删除所有这些 HTML,然后从头重新创建所有 DOM 节点。这可能更慢,会重置焦点和滚动位置,并且可能丢失其他用户输入。
服务端渲染的应用必须使用 hydrateRoot 而不是 createRoot:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(
document.getElementById('root'),
<App />
);请注意,它的 API 是不同的。尤其是,通常不会再有后续的 root.render 调用。