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
返回一个带有两个方法的的对象,这两个方法是:render
和 unmount
。
注意事项
- 如果你的应用程序是服务端渲染的,那么不能使用
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 />);
通常情况下,在项目启动阶段,你只需要运行下面的代码。它将会:
- 获取 HTML 中定义的DOM 节点。
- 在该 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 进行渲染。
渲染一个部分由 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 />);
举个例子,如果 domNode
是 null
,这意味着 For example, if domNode
is null
, it means that getElementById
返回了 null
。这意味着你在调用这个方法的时候,页面文档内并不存在指定的 id 对应的元素,于是就出现了这个问题。这里有一些可能的原因:
- 你使用的 id 可能和 HTML 文件中的 id 不同。请检查一下你的拼写是否正确!
- 打包构建产物的 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
调用。