hydrateRoot 允许你在浏览器 DOM 节点中显示 React 组件,该节点的 HTML 内容先前是由 react-dom/server. 生成的

const root = hydrateRoot(domNode, reactNode, options?)

参考

🌐 Reference

hydrateRoot(domNode, reactNode, options?)

调用 hydrateRoot 来“附加” React 到已经在服务器环境中由 React 渲染的现有 HTML。

🌐 Call hydrateRoot to “attach” React to existing HTML that was already rendered by React in a server environment.

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

const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, reactNode);

React 将附加到存在于 domNode 内的 HTML,并接管管理其中的 DOM。一个完全使用 React 构建的应用通常只会有一次 hydrateRoot 调用,并传入其根组件。

🌐 React will attach to the HTML that exists inside the domNode, and take over managing the DOM inside it. An app fully built with React will usually only have one hydrateRoot call with its root component.

查看更多示例。

参数

🌐 Parameters

  • domNode:在服务器上被渲染为根元素的 DOM 元素
  • reactNode:用于渲染现有 HTML 的“React 节点”。这通常是像 <App /> 这样的 JSX 片段,它是通过诸如 renderToPipeableStream(<App />)ReactDOM Server 方法渲染的。
  • 可选 options:用于此 React 根的选项对象。
    • 可选 onCaughtError:当 React 在错误边界捕获到错误时调用的回调。调用时会传入被错误边界捕获的 error,以及包含 componentStackerrorInfo 对象。
    • 可选 onUncaughtError:当抛出错误且未被错误边界捕获时调用的回调。调用时会传入被抛出的 error 和一个包含 componentStackerrorInfo 对象。
    • 可选 onRecoverableError:当 React 自动从错误中恢复时调用的回调。调用时会传入 React 抛出的 error,以及包含 componentStackerrorInfo 对象。一些可恢复的错误可能将原始错误原因包含为 error.cause
    • 可选 identifierPrefix:React 用于由 useId 生成的 ID 的字符串前缀。在同一页面使用多个根时,这有助于避免冲突。必须与服务器上使用的前缀相同。

返回

🌐 Returns

hydrateRoot 返回一个包含两个方法的对象:renderunmount

注意事项

🌐 Caveats

  • hydrateRoot() 期望渲染的内容与服务器渲染的内容完全相同。你应将不匹配视为错误并修复它们。
  • 在开发模式下,React 会在水合期间对不匹配情况发出警告。对于不匹配的情况,不能保证属性差异会被修补。这对于性能原因很重要,因为在大多数应用中,不匹配情况很少,因此验证所有标记的成本将非常高。
  • 在你的应用中,你很可能只会有一个 hydrateRoot 调用。如果你使用框架,它可能会为你执行这个调用。
  • 如果你的应用是客户端渲染且没有预先渲染的 HTML,则不支持使用 hydrateRoot()。请改用 createRoot()

root.render(reactNode)

调用 root.render 来更新位于已水合的 React 根中的浏览器 DOM 元素的 React 组件。

🌐 Call root.render to update a React component inside a hydrated React root for a browser DOM element.

root.render(<App />);

React 会在已水合的 root 中更新 <App />

🌐 React will update <App /> in the hydrated root.

查看更多示例。

参数

🌐 Parameters

  • reactNode:你想要更新的“React 节点”。这通常是像 <App /> 这样的 JSX 片段,但你也可以传入使用 createElement() 构建的 React 元素、一个字符串、一个数字、nullundefined

返回

🌐 Returns

root.render 返回 undefined

注意事项

🌐 Caveats

  • 如果在根节点完成水化之前调用 root.render,React 将清除现有的服务器渲染 HTML 内容,并将整个根节点切换到客户端渲染。

root.unmount()

调用 root.unmount 来销毁 React 根中的已渲染树。

🌐 Call root.unmount to destroy a rendered tree inside a React root.

root.unmount();

一个完全用 React 构建的应用通常不会有任何对 root.unmount 的调用。

🌐 An app fully built with React will usually not have any calls to root.unmount.

这主要在你的 React 根 DOM 节点(或其任何祖级节点)可能被其他代码从 DOM 中移除时有用。例如,想象一个 jQuery 选项卡面板会从 DOM 中移除不活跃的选项卡。如果一个选项卡被移除,它内部的所有内容(包括其中的 React 根)也会被从 DOM 中移除。你需要通过调用 root.unmount 告诉 React “停止” 管理被移除根节点的内容。否则,被移除根节点内部的组件将无法清理并释放资源,比如订阅。

🌐 This is mostly useful if your React root’s DOM node (or any of its ancestors) may get removed from the DOM by some other code. For example, imagine a jQuery tab panel that removes inactive tabs from the DOM. If a tab gets removed, everything inside it (including the React roots inside) would get removed from the DOM as well. You need to tell React to “stop” managing the removed root’s content by calling root.unmount. Otherwise, the components inside the removed root won’t clean up and free up resources like subscriptions.

调用 root.unmount 会卸载根中的所有组件,并将 React 从根 DOM 节点中“分离”,包括移除树中的任何事件处理程序或状态。

🌐 Calling root.unmount will unmount all the components in the root and “detach” React from the root DOM node, including removing any event handlers or state in the tree.

参数

🌐 Parameters

root.unmount不接受任何参数。

返回

🌐 Returns

root.unmount 返回 undefined

注意事项

🌐 Caveats

  • 调用 root.unmount 会卸载树中的所有组件,并将 React 从根 DOM 节点“分离”。
  • 一旦你调用 root.unmount,就不能再在根上调用 root.render。尝试在未挂载的根上调用 root.render 会抛出“无法更新未挂载的根”错误。

用法

🌐 Usage

Hydrating 服务器渲染的 HTML

🌐 Hydrating server-rendered HTML

如果你的应用的 HTML 是由 react-dom/server 生成的,你需要在客户端对其进行水化

🌐 If your app’s HTML was generated by react-dom/server, you need to hydrate it on the client.

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

hydrateRoot(document.getElementById('root'), <App />);

这将使用你的应用的 React 组件浏览器 DOM 节点 内对服务器 HTML 进行水合。通常,你会在启动时只做一次。如果你使用框架,它可能会在后台为你执行此操作。

为了给你的应用提供“水化”,React 会将你的组件逻辑“附加”到服务器生成的初始 HTML 上。水化将来自服务器的初始 HTML 快照转变为在浏览器中运行的完全交互式应用。

🌐 To hydrate your app, React will “attach” your components’ logic to the initial generated HTML from the server. Hydration turns the initial HTML snapshot from the server into a fully interactive app that runs in the browser.

import './styles.css';
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(
  document.getElementById('root'),
  <App />
);

你不应该再需要调用 hydrateRoot 或在更多地方调用它。从这一点开始,React 将管理你应用的 DOM。要更新 UI,你的组件将改为使用状态

🌐 You shouldn’t need to call hydrateRoot again or to call it in more places. From this point on, React will be managing the DOM of your application. To update the UI, your components will use state instead.

易犯错误

你传给 hydrateRoot 的 React 树需要生成 与服务器上相同的输出

🌐 The React tree you pass to hydrateRoot needs to produce the same output as it did on the server.

这对用户体验非常重要。用户在你的 JavaScript 代码加载之前,会花一些时间查看服务器生成的 HTML。服务器渲染通过显示其输出的 HTML 快照,创造了一种应用加载更快的错觉。突然显示不同的内容会打破这种错觉。这就是为什么服务器渲染的输出必须与客户端的初始渲染输出相匹配。

🌐 This is important for the user experience. The user will spend some time looking at the server-generated HTML before your JavaScript code loads. Server rendering creates an illusion that the app loads faster by showing the HTML snapshot of its output. Suddenly showing different content breaks that illusion. This is why the server render output must match the initial render output on the client.

导致水合作用错误的最常见原因包括:

🌐 The most common causes leading to hydration errors include:

  • 根节点内 React 生成的 HTML 周围的额外空格(如换行符)。
  • 在你的渲染逻辑中使用类似 typeof window !== 'undefined' 的检查。
  • 在渲染逻辑中使用仅浏览器可用的 API,例如 window.matchMedia
  • 在服务器和客户端上渲染不同的数据。

React 可以从一些 hydration 错误中恢复,但**你必须像修复其他错误一样修复它们。**在最好的情况下,它们会导致速度变慢;在最坏的情况下,事件处理程序可能会绑定到错误的元素上。

🌐 React recovers from some hydration errors, but you must fix them like other bugs. In the best case, they’ll lead to a slowdown; in the worst case, event handlers can get attached to the wrong elements.


Hydrating 整个文档

🌐 Hydrating an entire document

完全用 React 构建的应用可以将整个文档呈现为 JSX,包括 <html> 标签:

🌐 Apps fully built with React can render the entire document as JSX, including the <html> tag:

function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}

要为整个文档提供数据,请将 document 全局作为 hydrateRoot 的第一个参数传递:

🌐 To hydrate the entire document, pass the document global as the first argument to hydrateRoot:

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

hydrateRoot(document, <App />);

抑制不可避免的水合作用不匹配错误

🌐 Suppressing unavoidable hydration mismatch errors

如果单个元素的属性或文本内容在服务器和客户端之间不可避免地不同(例如,时间戳),你可以关闭水合作用不匹配警告。

🌐 If a single element’s attribute or text content is unavoidably different between the server and the client (for example, a timestamp), you may silence the hydration mismatch warning.

要在一个元素上静音水化警告,请添加 suppressHydrationWarning={true}

🌐 To silence hydration warnings on an element, add suppressHydrationWarning={true}:

export default function App() {
  return (
    <h1 suppressHydrationWarning={true}>
      Current Date: {new Date().toLocaleDateString()}
    </h1>
  );
}

这只作用于一层深度,并且旨在作为应急出口。不要过度使用它。React 不会尝试修补不匹配的文本内容。

🌐 This only works one level deep, and is intended to be an escape hatch. Don’t overuse it. React will not attempt to patch mismatched text content.


处理不同的客户端和服务器内容

🌐 Handling different client and server content

如果你有意需要在服务器和客户端上渲染不同的内容,你可以进行两遍渲染。在客户端渲染不同内容的组件可以读取像 isClient 这样的状态变量,你可以在Effect中将其设置为 true

🌐 If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a state variable like isClient, which you can set to true in an Effect:

import { useState, useEffect } from "react";

export default function App() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  return (
    <h1>
      {isClient ? 'Is Client' : 'Is Server'}
    </h1>
  );
}

这样,初始渲染通道将渲染与服务器相同的内容,避免不匹配,但附加通道将在水合后立即同步发生。

🌐 This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration.

易犯错误

这种方法使得水合过程变慢,因为你的组件必须渲染两次。在网络连接较慢时,要注意用户体验。JavaScript 代码可能会比初始 HTML 渲染晚很多加载,因此在水合之后立即渲染不同的用户界面,也可能会让用户感到不适应。

🌐 This approach makes hydration slower because your components have to render twice. Be mindful of the user experience on slow connections. The JavaScript code may load significantly later than the initial HTML render, so rendering a different UI immediately after hydration may also feel jarring to the user.


更新水合根组件

🌐 Updating a hydrated root component

在根节点完成水合后,你可以调用 root.render 来更新根 React 组件。createRoot 不同,你通常不需要这么做,因为初始内容已经作为 HTML 渲染了。

🌐 After the root has finished hydrating, you can call root.render to update the root React component. Unlike with createRoot, you don’t usually need to do this because the initial content was already rendered as HTML.

如果你在水合之后的某个时间调用 root.render,并且组件树结构与之前渲染的结构相匹配,React 将 保留状态。注意你可以在输入框中输入内容,这意味着在本示例中每秒重复调用 render 的更新不会破坏状态:

🌐 If you call root.render at some point after hydration, and the component tree structure matches up with what was previously rendered, React will preserve the state. Notice how you can type in the input, which means that the updates from repeated render calls every second in this example are not destructive:

import { hydrateRoot } from 'react-dom/client';
import './styles.css';
import App from './App.js';

const root = hydrateRoot(
  document.getElementById('root'),
  <App counter={0} />
);

let i = 0;
setInterval(() => {
  root.render(<App counter={i} />);
  i++;
}, 1000);

在已水化的根节点上调用 root.render 不常见。通常,你会在某个组件内部 更新状态

🌐 It is uncommon to call root.render on a hydrated root. Usually, you’ll update state inside one of the components instead.

生产中的错误日志记录

🌐 Error logging in production

默认情况下,React 会将所有错误记录到控制台。要实现你自己的错误报告,你可以提供可选的错误处理根选项 onUncaughtErroronCaughtErroronRecoverableError

🌐 By default, React will log all errors to the console. To implement your own error reporting, you can provide the optional error handler root options onUncaughtError, onCaughtError and onRecoverableError:

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import { reportCaughtError } from "./reportError";

const container = document.getElementById("root");
const root = hydrateRoot(container, <App />, {
onCaughtError: (error, errorInfo) => {
if (error.message !== "Known error") {
reportCaughtError({
error,
componentStack: errorInfo.componentStack,
});
}
},
});

onCaughtError 选项是一个使用两个参数调用的函数:

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

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

🌐 Together with onUncaughtError and onRecoverableError, you can implement your own error reporting system:

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {
  onCaughtErrorProd,
  onRecoverableErrorProd,
  onUncaughtErrorProd,
} from "./reportError";

const container = document.getElementById("root");
hydrateRoot(container, <App />, {
  // Keep in mind to remove these options in development to leverage
  // React's default handlers or implement your own overlay for development.
  // The handlers are only specfied unconditionally here for demonstration purposes.
  onCaughtError: onCaughtErrorProd,
  onRecoverableError: onRecoverableErrorProd,
  onUncaughtError: onUncaughtErrorProd,
});

故障排除

🌐 Troubleshooting

我收到一个错误:“你向 root.render 传递了第二个参数”

🌐 I’m getting an error: “You passed a second argument to root.render”

一个常见的错误是将 hydrateRoot 的选项传递给 root.render(...)

🌐 A common mistake is to pass the options for hydrateRoot to root.render(...):

Console

要修复,请将根选项传递给 hydrateRoot(...),而不是 root.render(...)

🌐 To fix, pass the root options to hydrateRoot(...), not root.render(...):

// 🚩 Wrong: root.render only takes one argument.
root.render(App, {onUncaughtError});

// ✅ Correct: pass options to createRoot.
const root = hydrateRoot(container, <App />, {onUncaughtError});