
createRoot()
markdown
---
title: createRoot()
---
<Intro>
`createRoot` 允许在浏览器的 DOM 节点中创建根节点以显示 React 组件。
```js
const root = createRoot(domNode, options?)
参考 {/reference/}
createRoot(domNode, options?) {/createroot/}
调用 createRoot 以在浏览器 DOM 元素中创建根节点显示内容。
js
import { createRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = createRoot(domNode);
React 将会为 domNode 创建一个根节点,并控制其中的 DOM。在已经创建根节点之后,需要调用 root.render 来显示 React 组件:
js
root.render(<App />);
对于一个完全用 React 构建的应用程序,通常会调用一个 createRoot 来创建它的根节点。而对于一个使用了“少量” React 来创建部分内容的应用程序,则要按具体需求来确定根节点的数量。
参数 {/parameters/}
-
domNode:一个 DOM 元素。React 将为这个 DOM 元素创建一个根节点然后允许你在这个根节点上调用函数,比如render来显示渲染的 React 内容。 -
可选
options:用于配置这个 React 根节点的对象。- 可选
onCaughtError:当 React 在错误边界中捕获到错误时调用的回调。回调参数为错误边界捕获的error和一个包含componentStack的errorInfo对象。 - 可选
onUncaughtError:当抛出错误且未被错误边界捕获时调用的回调。回调参数为抛出的error和一个包含componentStack的errorInfo对象。 - 可选
onRecoverableError:当 React 自动从错误中恢复时调用的回调。回调参数为 React 抛出的error和一个包含componentStack的errorInfo对象。某些可恢复错误可能包含原始错误原因作为error.cause。 - 可选
identifierPrefix:一个字符串前缀,React 用于useId生成的 ID。当页面有多个根节点时,可用于避免冲突。
- 可选
返回值 {/returns/}
createRoot 返回一个带有两个方法的的对象,这两个方法是:render 和 unmount。
注意 {/caveats/}
- 如果应用程序是服务端渲染的,那么不能使用
createRoot()。请使用hydrateRoot()替代它。 - 在你的应用程序中,可能只调用了一次
createRoot。但如果你使用了框架,它可能已经自动帮你完成了这次调用。 - 当你想要渲染一段 JSX,但是它存在于 DOM 树的其他位置,并非当前组件的子组件时(比如,一个弹窗或者提示框),使用
createPortal替代createRoot。
root.render(reactNode) {/root-render/}
调用 root.render 以将一段 JSX(“React 节点”)在 React 的根节点中渲染为 DOM 节点并显示。
js
root.render(<App />);
React 将会在 根节点 中显示 <App /> 组件,并且控制组件中的 DOM。
参数 {/root-render-parameters/}
reactNode:一个你想要显示的 React 节点。它总是一段 JSX,就像<App />,但是你也总是可以传递一个createElement()构造的 React 元素、一个字符串、一个数字、null或者undefined。
返回值 {/root-render-returns/}
root.render 返回 undefined。
注意 {/root-render-caveats/}
-
首次调用
root.render时,React 会先清空根节点中所有已经存在的 HTML,然后才会渲染 React 组件。 -
如果你的根节点包含了由 React 在构建期间通过服务端渲染生成的 HTML 内容,请使用
hydrateRoot()替代这个方法,这样才能把事件处理程序和现有的 HTML 绑定。
如果你在一个根节点上多次调用了 render,React 仍然会更新 DOM,这样才能保证显示的内容是最新的。React 将会筛选出可复用的部分和需要更新的部分,对于需要更新的部分,是 React 通过与之前渲染的树进行 “比较” 得到的。在同一个根节点上再次调用 render 就和在根节点上调用 set 函数 类似:React 会避免没必要的 DOM 更新。
- 虽然渲染一旦开始就是同步的,但
root.render(...)本身不是同步的。这意味着root.render()之后的代码可能会在该次渲染的任何副作用(useLayoutEffect、useEffect)触发之前执行。这通常是没问题的,很少需要调整。在极少数情况下,如果副作用的时机很重要,你可以将root.render(...)包装在flushSync中,以确保初始渲染完全同步完成。
js
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// 🚩 HTML 还不包含已经渲染的 <App />:
console.log(document.body.innerHTML);
root.unmount() {/root-unmount/}
调用 root.unmount 以销毁 React 根节点中的一个已经渲染的树。
js
root.unmount();
通常情况下,一个完全由 React 构建的应用程序不会调用 root.unmount。
此方法适用的场景是,React 根节点中的 DOM 节点(或者它的任何一个父级节点)被除了这个方法以外的代码移除了。举个例子,试想在一个 jQuery 选项卡面板中,非活跃状态的选项卡的 DOM 结构将被移除。一个标签页被移除时,它内部的所有内容(包括 React 根节点)也将会从 DOM 树移除。在这种情况下,你才需要调用 root.unmount 来通知 React “停止”控制已经被移除的根节点的内容。否则,被移除的根节点的内部组件就不能及时释放消息订阅等资源。
调用 root.unmount 将卸载根节点内的所有组件,该根节点上的 React 将被剥离,即所有事件处理程序以及组件树上的状态将被移除。
返回值 {/root-unmount-returns/}
root.unmount 返回 undefined。
注意事项 {/root-unmount-caveats/}
-
调用
root.unmount将卸载根节点内的所有组件,该根节点上的 React 将被剥离,即所有事件处理程序以及组件树上的状态将被移除。 -
一旦调用
root.unmount,就不能在该根节点上调用root.render。在一个已经卸载的根节点上尝试调用root.render将会抛出异常错误信息“无法更新一个未挂载的根节点(Cannot update an unmounted root)”。不过,你可以在卸载一个根节点后又重新创建它。
用法 {/usage/}
渲染一个完全由 React 构建的应用 {/rendering-an-app-fully-built-with-react/}
如果应用程序是完全由 React 构建的,那么请为整个应用程序创建全局唯一的一个根节点。
js [[1, 3, "document.getElementById('root')"], [2, 4, "<App />"]]
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
通常情况下,在项目启动阶段,你只需要运行下面的代码。它将会:
- 获取 HTML 中定义的
DOM 节点 。 - 在该 DOM 节点中显示
React 组件 。
html public/index.html
<!DOCTYPE html>
<html>
<head><title>My app</title></head>
<body>
<!-- 这就是我们提到的 DOM 节点 -->
<div id="root"></div>
</body>
</html>
js src/index.js active
import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
js src/App.js
import { useState } from 'react';
export default function App() {
return (
<>
<h1>你好,世界!</h1>
<Counter />
</>
);
}
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
点击了 {count} 次
</button>
);
}
如果你的应用程序完全由 React 构建,你仅应该创建全局唯一的一个根节点,并只调用一次 root.render。
从这时起,React 将会控制整个应用程序的 DOM。如果要添加更多组件,可以将它们嵌套进 App 组件中。如果你需要更新视图,每一个组件都可以通过使用 state 做到这一点。如果你需要额外显示一些在这个 DOM 节点之外的内容,比如一个弹窗或者提示框,那么可以 使用 portal 进行渲染。
当 HTML 为空时,用户将会看一个空白的页面,直到应用程序中的 JavaScript 代码加载并运行:
html
<div id="root"></div>
这个过程太慢了!要解决这个问题,可以在 服务端或者应用构建期间 通过组件生成一些初始 HTML。这样一来,在 JavaScript 加载之前,用户就能看到一些文字、图片,也能点击链接。我们推荐 使用框架,通过框架开箱即用的能力轻易地完成这个优化。根据框架运行的时机,分为 服务端渲染(SSR) 和 静态站点生成(SSG)。
使用了服务端渲染或者静态站点生成的应用程序,必须调用 hydrateRoot 而不是 createRoot。调用后,React 将会 hydrate HTML 中的 DOM 节点,而不是销毁后重新创建它们。
渲染一个部分由 React 构建的应用 {/rendering-a-page-partially-built-with-react/}
如果页面 不完全是 React 构建的,可以多次调用 createRoot 为每一个由 React 管理的顶级视图片段,即一段 DOM 中的顶级节点,创建一个根节点。可以在每一个根节点中调用 root.render 来显示不同的内容。
这样一来,两个不同的 React 组件分别在同一个文件 index.html 内定义的两个 DOM 节点中被渲染:
html public/index.html
<!DOCTYPE html>
<html>
<head><title>My app</title></head>
<body>
<nav id="navigation"></nav>
<main>
<p>这一段不会被 React 渲染(可以打开 index.html 验证这一点)。</p>
<section id="comments"></section>
</main>
</body>
</html>
js src/index.js active
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 />);
js src/Components.js
export function Navigation() {
return (
<ul>
<NavLink href="/">Home</NavLink>
<NavLink href="/about">About</NavLink>
</ul>
);
}
function NavLink({ href, children }) {
return (
<li>
<a href={href}>{children}</a>
</li>
);
}
export function Comments() {
return (
<>
<h2>Comments</h2>
<Comment text="你好!" author="Sophie" />
<Comment text="最近怎么样?" author="Sunil" />
</>
);
}
function Comment({ text, author }) {
return (
<p>{text} — <i>{author}</i></p>
);
}
css
nav ul { padding: 0; margin: 0; }
nav ul li { display: inline-block; margin-right: 20px; }
也可以通过 document.createElement() 创建一个新的 DOM 节点然后手动将其加入页面文档之中。
js
const domNode = document.createElement('div');
const root = createRoot(domNode);
root.render(<Comment />);
document.body.appendChild(domNode); // 你可以把它加入到页面文档的任何位置
调用 root.unmount 将从 DOM 节点移除这个 React 树并清除它使用的资源。
js
root.unmount();
如果 React 组件处于一个由不同框架构建的应用程序之中时,那么这个方法将会非常有用。
更新一个根组件 {/updating-a-root-component/}
可以在同一个根节点上多次调用 render。只要当前的组件树结构与之前渲染的结果是一致的,React 将会 保存 state。仔细思考一下,为什么能在输入框中正常输入?正如下方的示例,在每一秒内多次重复调用 render 后发生的更新,是非破坏性的:
js src/index.js active
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);
js src/App.js
export default function App({counter}) {
return (
<>
<h1>你好,世界!{counter}</h1>
<input placeholder="在这里输入一些内容" />
</>
);
}
多次调用 render 是一个不常见的事情。通常情况下,你的组件将通过 更新 state 达到同样的效果。
生产环境中的错误日志记录 {/error-logging-in-production/}
默认情况下,React 会将所有错误记录到控制台。为了实现你自己的错误报告,你可以提供可选的错误处理根节点选项 onUncaughtError、onCaughtError 和 onRecoverableError:
js [[1, 6, "onCaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack", 15]]
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,
});
}
},
});
- 被抛出的
error 。 - 一个
errorInfo 对象,包含错误的componentStack 。
结合 onUncaughtError 和 onRecoverableError,你可以实现自己的错误报告系统:
js src/reportError.js
function reportError({ type, error, errorInfo }) {
// 具体的实现由你决定。
// 这里使用 `console.error()` 仅用于演示。
console.error(type, error, "Component Stack: ");
console.error("Component Stack: ", errorInfo.componentStack);
}
export function onCaughtErrorProd(error, errorInfo) {
if (error.message !== "Known error") {
reportError({ type: "Caught", error, errorInfo });
}
}
export function onUncaughtErrorProd(error, errorInfo) {
reportError({ type: "Uncaught", error, errorInfo });
}
export function onRecoverableErrorProd(error, errorInfo) {
reportError({ type: "Recoverable", error, errorInfo });
}
js src/index.js active
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 />);
js src/App.js
import { Component, useState } from "react";
function Boom() {
foo.bar = "baz";
}
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default function App() {
const [triggerUncaughtError, settriggerUncaughtError] = useState(false);
const [triggerCaughtError, setTriggerCaughtError] = useState(false);
return (
<>
<button onClick={() => settriggerUncaughtError(true)}>
Trigger uncaught error
</button>
{triggerUncaughtError && <Boom />}
<button onClick={() => setTriggerCaughtError(true)}>
Trigger caught error
</button>
{triggerCaughtError && (
<ErrorBoundary>
<Boom />
</ErrorBoundary>
)}
</>
);
}
错误排查 {/troubleshooting/}
我已经创建了一个根节点,但是页面没有显示任何内容 {/ive-created-a-root-but-nothing-is-displayed/}
确保你没有忘记在根节点上 渲染 你的应用程序:
js {5}
import { createRoot } from 'react-dom/client';
import App from './App.js';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
在你进行渲染之前,页面不会显示任何内容。
我遇到了一个错误:“你给 root.render 传递了第二个参数” {/im-getting-an-error-you-passed-a-second-argument-to-root-render/}
一个常见的错误是将 createRoot 的选项传递给了 root.render(...):
Warning: You passed a second argument to root.render(...) but it only accepts one argument.
正确的做法是将根节点选项传递给 createRoot(...),而不是 root.render(...):
js {2,5}
// 🚩 错误:root.render 只接受一个参数。
root.render(App, {onUncaughtError});
// ✅ 正确:将选项传递给 createRoot。
const root = createRoot(container, {onUncaughtError});
root.render(<App />);
我遇到了一个错误:“目标容器不是一个 DOM 元素” {/im-getting-an-error-target-container-is-not-a-dom-element/}
这个异常错误即字面意思,你传递给 createRoot 的内容不是一个 DOM 元素。
如果你不确定发生了什么,试着在控制台打印它:
js {2}
const domNode = document.getElementById('root');
console.log(domNode); // ???
const root = createRoot(domNode);
root.render(<App />);
举个例子,如果 domNode 是 null,代表着 getElementById 返回了 null。这意味着你在调用这个方法的时候,页面文档内并不存在指定的 id 对应的元素,于是就出现了这个问题。这里有一些可能的原因:
- 你使用的 id 可能和 HTML 文件中的 id 不同。请检查一下你的拼写是否正确!
- 打包构建产物的 HTML 文件中的
<script>标签,不能感知到在它执行 之后 才出现的 DOM 节点。
触发这个异常报错的另一个常见方式是,将 createRoot(domNode) 错写成 createRoot(<App />)。
我得到了一个异常报错信息:“函数不是合法的 React 子项” {/im-getting-an-error-functions-are-not-valid-as-a-react-child/}
这个错误意味着,你传递给 root.render 的内容不是一个 React 组件。
这可能发生在你使用 Component 而不是 <Component /> 作为参数对 root.render 进行调用时:
js {2,5}
// 🚩 错误方式:App 是一个函数,不是一个组件。
root.render(App);
// ✅ 正确方式:<App /> 是一个组件。
root.render(<App />);
或者你向 root.render 传递了一个函数本身,而不是其返回值:
js {2,5}
// 🚩 错误方式:createApp 是一个函数,不是一个组件。
root.render(createApp);
// ✅ 正确方式:使用 createApp 执行后返回的组件。
root.render(createApp());
我的服务端渲染的 HTML 在更新时会全部重新创建 {/我的服务端渲染的-html-在更新时会全部重新创建/}
如果你的应用程序是服务端渲染的,并且包含了由 React 生成的初始 HTML,你可能会注意到,在创建一个根节点后调用 root.render 时会删除所有初始 HTML,然后重新生成所有 DOM 节点。这可能会让你的应用变得比客户端渲染更慢,并且在用户输入失去焦点和滑动滚动条时丢失用户输入的内容。
服务端渲染的应用程序必须使用 hydrateRoot 替代 createRoot:
js {1,4-7}
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(
document.getElementById('root'),
<App />
);
请注意,服务端渲染的 API 是不同的。特别注意,在通常情况下,不会再有 root.render 调用。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
