createRoot 允许你在一个浏览器的 DOM 节点中创建一个根节点来显示你的 React 组件。

const root = createRoot(domNode, options?)

参考

createRoot(domNode, options?)

调用 createRoot 来在一个浏览器 DOM 元素中创建一个根节点用于显示内容。

import { createRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = createRoot(domNode);

React 将会为 domNode 创建一个根节点,并控制其中的 DOM。在你已经创建了一个根节点之后,你需要调用 root.render 来显示其中的一个 React 组件:

root.render(<App />);

对于一个完全用 React 构建的应用程序,通常会调用一个 createRoot 来创建它的根节点。而对于一个使用了“少量” React 来创建部分内容的应用程序,则要按具体需求来确定根节点的数量。

参阅下方更多示例

参数

  • domNode:一个 DOM 元素。React 将为这个 DOM 元素创建一个根节点然后允许你在这个根节点上调用函数,比如 render 来显示渲染的 React 内容。

  • 可选 options:一个用于配置这个 React 根节点的对象。

    • 可选 onRecoverableError:回调函数,在 React 从异常错误中恢复时自动调用。
    • 可选 identifierPrefix:一个 React 用来配合 useId 生成 id 的字符串前缀。在同一个页面上使用多个根节点的场景下,这将能有效避免冲突。

返回值

createRoot 返回一个带有两个方法的的对象,这两个方法是:renderunmount

注意事项

  • 如果你的应用程序是服务端渲染的,那么不能使用 createRoot()。请使用 hydrateRoot() 替代它。
  • 在你的应用程序中,可能只调用了一次 createRoot。但如果你使用了框架,它可能已经自动帮你完成了这次调用。
  • 当你想要渲染一段 JSX,但是它存在于 DOM 树的其他位置,并非当前组件的子组件时(比如,一个弹窗或者提示框),使用 createPortal 替代 createRoot

root.render(reactNode)

调用 root.render 来将一段 JSX (“React 节点”)在 React 的根节点中渲染为 DOM 节点从而能够显示。

root.render(<App />);

React 将会在 根节点 中显示 <App /> 组件,并且控制组件中的 DOM。

参阅下方查看更多示例

参数

  • reactNode:一个你想要显示的 React 节点。它总是一段 JSX,就像 <App />,但是你也总是可以传递一个 createElement() 构造的 React 元素、一个字符串、一个数字、null 或者 undefined

返回值

root.render 返回 undefined

注意事项

  • 首次调用 root.render 时,React 先会清空根节点中所有已经存在的 HTML,然后才会渲染 React 组件。

  • 如果你的根节点包含了由 React 在构建期间通过服务端渲染生成的 HTML 内容,请使用 hydrateRoot() 替代这个方法,这样才能把事件处理程序和现有的 HTML 绑定。

如果你在一个根节点上多次调用了 render,React 仍然会更新 DOM,这样才能保证显示的内容是最新的。React 将会筛选出可复用的部分和需要更新的部分,对于需要更新的部分,是 React 通过与之前渲染的树进行 “比较” 得到的。在同一个根节点上再次调用 render 就和在根节点上调用 set 函数 类似:React 会避免没有必要的 DOM 更新。


root.unmount()

调用 root.unmount 来销毁 React 根节点中的一个已经渲染的树。

root.unmount();

通常情况下,一个完全由 React 构建的应用程序不会调用 root.unmount

这个方法适用的场景是,你的 React 根节点中的 DOM 节点(或者它的任何一个父级节点)被除了这个方法以外的代码移除了。举个例子,试想在一个 jQuery 选项卡面板中,非活跃状态的选项卡的 DOM 结构将被移除。一个标签页被移除时,它内部的所有内容(包括 React 根节点)也将会从 DOM 树移除。在这种情况下,你才需要调用 root.unmount 来通知 React “停止”控制已经被移除的根节点的内容。否则,被移除的根节点的内部组件就不能及时释放消息订阅等资源。

调用 root.unmout 将卸载根节点内的所有组件,该根节点上的 React 将被剥离,即所有事件处理程序以及组件树上的状态将被移除。

参数

root.unmount 不接收任何参数。

返回值

root.unmount 返回 undefined

注意事项

  • 调用 root.unmout 将卸载根节点内的所有组件,该根节点上的 React 将被剥离,即所有事件处理程序以及组件树上的状态将被移除。

  • 一旦你调用了 root.unmout,你就不能在该根节点上调用 root.render。在一个已经卸载的根节点上尝试调用 root.render 将会抛出异常错误信息“无法更新一个未挂载的根节点(Cannot update an unmouted root)”。不过,你可以在卸载一个根节点后又重新创建它。


用法

渲染一个完全由 React 构建的应用

如果你的应用程序是完全由 React 构建的,那么请为整个应用程序创建全局唯一的一个根节点。

import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

通常情况下,在项目启动阶段,你只需要运行下面的代码。它将会:

  1. 获取 HTML 中定义的DOM 节点
  2. 在该 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.render

从这时起,React 将会控制你整个应用程序的 DOM。如果要添加更多组件,可以将它们嵌套进 App 组件中。如果你需要更新视图,每一个组件都可以通过使用 状态 做到这一点。如果你需要额外显示一些在这个 DOM 节点之外的内容,比如一个弹窗或者提示框,那么可以 使用 portal 进行渲染

注意

当你的 HTML 为空时,用户将会看一个空白的页面,直到应用程序中的 JavaScript 代码加载并运行:

<div id="root"></div>

这个过程太慢了!要解决这个问题,你可以在服务端或者应用构建期间,通过组件生成一些初始 HTML,这样一来,在 JavaScript 加载之前,用户就能看到一些文字、图片,也能点击链接。我们推荐 使用一个框架,通过其开箱即用的能力轻易地完成这个优化。根据框架运行的时机,分为 服务端渲染(SSR)静态站点生成(SSG)

陷阱

使用了服务端渲染或者静态站点生成的应用程序,必须调用 hydrateRoot 而不是 createRoot。调用后,React 将会 hydrate (复用) HTML 中的 DOM 节点,而不是销毁后重新创建它们。


渲染一个部分由 React 构建的应用

如果你的页面 不完全是 React 构建的,你可以多次调用 createRoot 为每一个由 React 管理的顶级视图片段,即一段 DOM 中的顶级节点,创建一个根节点。你可以在每一个根节点中调用 root.render 来显示不同的内容。

这样依赖,两个不同的 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); // 你可以把它加入到页面文档的任何位置

调用 root.unmount 将从 DOM 节点移除这个 React 树并清除它使用的资源。

root.unmount();

如果你的 React 组件是处于一个由不同框架构建的应用程序之中时,那么这个方法将会非常有用。


更新一个根组件

你可以在同一个根节点上多次调用 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 是一个不常见的事情。通常情况下,你的组件将通过 更新状态 达到同样的效果。


错误排查

我已经创建了一个根节点,但是页面没有显示任何内容

确保你没有忘记在根节点上 渲染 你的应用程序:

import { createRoot } from 'react-dom/client';
import App from './App.js';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

在你进行渲染之前,页面不会显示任何内容。


我得到了一个异常报错信息:“目标容器不是一个 DOM 元素(Target container is not a DOM element)”

这个异常错误即字面意思,你传递给 createRoot 的内容不是一个 DOM 元素。

如果你不确定发生了什么,试着在控制台打印它:

const domNode = document.getElementById('root');
console.log(domNode); // ???
const root = createRoot(domNode);
root.render(<App />);

举个例子,如果 domNodenull,这意味着 For example, if domNode is null, it means that getElementById 返回了 null。这意味着你在调用这个方法的时候,页面文档内并不存在指定的 id 对应的元素,于是就出现了这个问题。这里有一些可能的原因:

  1. 你使用的 id 可能和 HTML 文件中的 id 不同。请检查一下你的拼写是否正确!
  2. 打包构建产物的 HTML 文件中的 <script> 标签,不能感知到在它执行 之后 才出现的 DOM 节点。 Your bundle’s <script> tag cannot “see” any DOM nodes that appear after it in the HTML.

触发这个异常报错的另一个常见方式是,将 createRoot(domNode) 错写成 createRoot(<App />)


我得到了一个异常报错信息:“函数不是合法的 React 子项(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 在更新时会全部重新创建

My server-rendered HTML gets re-created from scratch

如果你的应用程序是服务端渲染的,并且包含了由 React 生成的初始 HTML,你可能会注意到,在创建一个根节点后调用 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 调用。