知海

createRoot()

ReactAPI 参考:React DOM
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 和一个包含 componentStackerrorInfo 对象。
    • 可选 onUncaughtError:当抛出错误且未被错误边界捕获时调用的回调。回调参数为抛出的 error 和一个包含 componentStackerrorInfo 对象。
    • 可选 onRecoverableError:当 React 自动从错误中恢复时调用的回调。回调参数为 React 抛出的 error 和一个包含 componentStackerrorInfo 对象。某些可恢复错误可能包含原始错误原因作为 error.cause
    • 可选 identifierPrefix:一个字符串前缀,React 用于 useId 生成的 ID。当页面有多个根节点时,可用于避免冲突。

返回值 {/returns/}

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

注意 {/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() 之后的代码可能会在该次渲染的任何副作用(useLayoutEffectuseEffect)触发之前执行。这通常是没问题的,很少需要调整。在极少数情况下,如果副作用的时机很重要,你可以将 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 />);

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

  1. 获取 HTML 中定义的DOM 节点
  2. 在该 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 会将所有错误记录到控制台。为了实现你自己的错误报告,你可以提供可选的错误处理根节点选项 onUncaughtErroronCaughtErroronRecoverableError

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,
      });
    }
  },
});

onCaughtError 选项是一个函数,它接收两个参数:

  1. 被抛出的 error
  2. 一个 errorInfo 对象,包含错误的 componentStack

结合 onUncaughtErroronRecoverableError,你可以实现自己的错误报告系统:

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 />);

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

  1. 你使用的 id 可能和 HTML 文件中的 id 不同。请检查一下你的拼写是否正确!
  2. 打包构建产物的 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 调用。

复制代码

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。