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

¥hydrateRoot lets you display React components inside a browser DOM node whose HTML content was previously generated by 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.

请参阅下面的更多示例。

¥See more examples below.

参数

¥Parameters

  • domNode:在服务器上渲染为根元素的 DOM 元素

    ¥domNode: A DOM element that was rendered as the root element on the server.

  • reactNode:“React 节点” 用于渲染现有的 HTML。这通常是一段像 <App /> 这样的 JSX,它是用 ReactDOM Server 方法渲染的,比如 renderToPipeableStream(<App />)

    ¥reactNode: The “React node” used to render the existing HTML. This will usually be a piece of JSX like <App /> which was rendered with a ReactDOM Server method such as renderToPipeableStream(<App />).

  • 可选 options:具有此 React 根选项的对象。

    ¥optional options: An object with options for this React root.

Canary only

可选 onCaughtError:当 React 在错误边界中捕获错误时调用的回调。使用被错误边界捕获的 error 和包含 componentStackerrorInfo 对象调用。*

¥optional onCaughtError: Callback called when React catches an error in an Error Boundary. Called with the error caught by the Error Boundary, and an errorInfo object containing the componentStack.

Canary only

可选 onUncaughtError:当抛出错误但未被错误边界捕获时调用的回调。使用抛出的 error 和包含 componentStackerrorInfo 对象调用。

¥optional onUncaughtError: Callback called when an error is thrown and not caught by an Error Boundary. Called with the error that was thrown and an errorInfo object containing the componentStack.

  • 可选 onRecoverableError:当 React 自动从错误中恢复时调用的回调。使用 React 抛出的 error 和包含 componentStackerrorInfo 对象调用。某些可恢复错误可能将原始错误原因包括为 error.cause

    ¥optional onRecoverableError: Callback called when React automatically recovers from errors. Called with the error React throws, and an errorInfo object containing the componentStack. Some recoverable errors may include the original error cause as error.cause.

  • 可选 identifierPrefix:React 用于 useId 生成的 ID 的字符串前缀 有助于避免在同一页面上使用多个根时发生冲突。必须与服务器上使用的前缀相同。

    ¥optional identifierPrefix: A string prefix React uses for IDs generated by useId. Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as used on the server.

返回

¥Returns

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

¥hydrateRoot returns an object with two methods: render and unmount.

注意事项

¥Caveats

  • hydrateRoot() 期望渲染的内容与服务器渲染的内容相同。你应该将不匹配视为错误并修复它们。

    ¥hydrateRoot() expects the rendered content to be identical with the server-rendered content. You should treat mismatches as bugs and fix them.

  • 在开发模式下,React 会在水合作用期间警告不匹配。不保证在不匹配的情况下会修补属性差异。出于性能原因,这很重要,因为在大多数应用中,不匹配的情况很少见,因此验证所有标记的成本非常高。

    ¥In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive.

  • 你的应用中可能只有一个 hydrateRoot 调用。如果你使用框架,它可能会为你执行此调用。

    ¥You’ll likely have only one hydrateRoot call in your app. If you use a framework, it might do this call for you.

  • 如果你的应用是客户端渲染的,但尚未渲染 HTML,则不支持使用 hydrateRoot()。请改用 createRoot()

    ¥If your app is client-rendered with no HTML rendered already, using hydrateRoot() is not supported. Use createRoot() instead.


root.render(reactNode)

调用 root.render 以更新浏览器 DOM 元素的水合 React 根内的 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.

请参阅下面的更多示例。

¥See more examples below.

参数

¥Parameters

  • reactNode:你要更新的 “React 节点”。这通常是一段 JSX,如 <App />,但你也可以传递一个由 createElement()、字符串、数字、nullundefined 构造的 React 元素。

    ¥reactNode: A “React node” that you want to update. This will usually be a piece of JSX like <App />, but you can also pass a React element constructed with createElement(), a string, a number, null, or undefined.

返回

¥Returns

root.render 返回 undefined

¥root.render returns undefined.

注意事项

¥Caveats

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

    ¥If you call root.render before the root has finished hydrating, React will clear the existing server-rendered HTML content and switch the entire root to client rendering.


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 到 “stop” 管理删除的根目录的内容。否则,删除的根中的组件将不会清理和释放订阅等资源。

¥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 将从根 DOM 节点卸载根中的所有组件和 “detach” React,包括删除树中的任何事件处理程序或状态。

¥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 没有参数。

¥root.unmount does not accept any parameters.

返回

¥Returns

root.unmount 返回 undefined

¥root.unmount returns undefined.

注意事项

¥Caveats

  • 调用 root.unmount 将从根 DOM 节点卸载树中的所有组件和 “detach” React。

    ¥Calling root.unmount will unmount all the components in the tree and “detach” React from the root DOM node.

  • 一旦调用 root.unmount,就不能在根目录上再次调用 root.render。尝试在未挂载的根上调用 root.render 将引发 “无法更新卸载的根目录” 错误。

    ¥Once you call root.unmount you cannot call root.render again on the root. Attempting to call root.render on an unmounted root will throw a “Cannot update an unmounted root” error.


用法

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

这会将 浏览器 DOM 节点 内的服务器 HTML 与你应用的 React 组件 结合起来。通常,你会在启动时执行一次。如果你使用一个框架,它可能会在幕后为你做这件事。

¥This will hydrate the server HTML inside the browser DOM node with the React component for your app. Usually, you will do it once at startup. If you use a framework, it might do this behind the scenes for you.

为了滋润你的应用,React 会将你组件的逻辑 “attach” 到服务器初始生成的 HTML 中。Hydration 将来自服务器的初始 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 周围的额外空格(如换行符)。

    ¥Extra whitespace (like newlines) around the React-generated HTML inside the root node.

  • 在渲染逻辑中使用像 typeof window !== 'undefined' 这样的检查。

    ¥Using checks like typeof window !== 'undefined' in your rendering logic.

  • 在渲染逻辑中使用 window.matchMedia 等仅限浏览器的 API。

    ¥Using browser-only APIs like window.matchMedia in your rendering logic.

  • 在服务器和客户端上渲染不同的数据。

    ¥Rendering different data on the server and the client.

React 可以从一些水合错误中恢复,但你必须像其他错误一样修复它们。在最好的情况下,它们会导致经济放缓;在最好的情况下,它们会导致经济放缓。在最坏的情况下,事件处理程序可能会附加到错误的元素上。

¥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. Unless it’s text content, React still won’t attempt to patch it up, so it may remain inconsistent until future updates.


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

¥Handling different client and server content

如果你有意需要在服务器和客户端上渲染不同的东西,你可以进行两次渲染。在客户端渲染不同内容的组件可以读取像 isClient 这样的 状态变量,你可以在 副作用 中将其设置为 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 渲染,因此在水合后立即渲染不同的 UI 也可能会让用户感到不协调。

¥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 完成 hydrating 后,你可以调用 root.render 来更新 root 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.

如果你在 hydration 之后的某个时刻调用 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.

显示未捕获错误的对话框

¥Show a dialog for uncaught errors

Canary

onUncaughtError 仅在最新的 React Canary 版本中可用。

¥onUncaughtError is only available in the latest React Canary release.

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

¥By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional onUncaughtError root option:

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

const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onUncaughtError: (error, errorInfo) => {
console.error(
'Uncaught error',
error,
errorInfo.componentStack
);
}
}
);
root.render(<App />);

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

¥The onUncaughtError option is a function called with two arguments:

  1. 抛出的 error

    ¥The error that was thrown.

  2. 包含错误的 componentStackerrorInfo 对象。

    ¥An errorInfo object that contains the componentStack of the error.

你可以使用 onUncaughtError 根选项显示错误对话框:

¥You can use the onUncaughtError root option to display error dialogs:

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {reportUncaughtError} from "./reportError";
import "./styles.css";
import {renderToString} from 'react-dom/server';

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

显示错误边界错误

¥Displaying Error Boundary errors

Canary

onCaughtError 仅在最新的 React Canary 版本中可用。

¥onCaughtError is only available in the latest React Canary release.

默认情况下,React 会将错误边界捕获的所有错误记录到 console.error。要覆盖此行为,你可以为 错误边界 捕获的错误提供可选的 onCaughtError 根选项:

¥By default, React will log all errors caught by an Error Boundary to console.error. To override this behavior, you can provide the optional onCaughtError root option for errors caught by an Error Boundary:

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

const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onCaughtError: (error, errorInfo) => {
console.error(
'Caught error',
error,
errorInfo.componentStack
);
}
}
);
root.render(<App />);

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

¥The onCaughtError option is a function called with two arguments:

  1. 被边界捕获的 error

    ¥The error that was caught by the boundary.

  2. 包含错误的 componentStackerrorInfo 对象。

    ¥An errorInfo object that contains the componentStack of the error.

你可以使用 onCaughtError 根选项显示错误对话框或从日志中过滤已知错误:

¥You can use the onCaughtError root option to display error dialogs or filter known errors from logging:

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

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

显示可恢复水合不匹配错误的对话框

¥Show a dialog for recoverable hydration mismatch errors

当 React 遇到水合不匹配时,它会自动尝试通过在客户端上进行渲染来恢复。默认情况下,React 会将水合不匹配错误记录到 console.error。要覆盖此行为,你可以提供可选的 onRecoverableError 根选项:

¥When React encounters a hydration mismatch, it will automatically attempt to recover by rendering on the client. By default, React will log hydration mismatch errors to console.error. To override this behavior, you can provide the optional onRecoverableError root option:

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

const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onRecoverableError: (error, errorInfo) => {
console.error(
'Caught error',
error,
error.cause,
errorInfo.componentStack
);
}
}
);

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

¥The onRecoverableError option is a function called with two arguments:

  1. React 抛出的 error。某些错误可能将原始原因包括为 error.cause

    ¥The error React throws. Some errors may include the original cause as error.cause.

  2. 包含错误的 componentStackerrorInfo 对象。

    ¥An errorInfo object that contains the componentStack of the error.

你可以使用 onRecoverableError 根选项显示水合不匹配的错误对话框:

¥You can use the onRecoverableError root option to display error dialogs for hydration mismatches:

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

const container = document.getElementById("root");
const root = hydrateRoot(container, <App />, {
  onRecoverableError: (error, errorInfo) => {
    reportRecoverableError({
      error,
      cause: error.cause,
      componentStack: errorInfo.componentStack
    });
  }
});

故障排除

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

React 中文网 - 粤ICP备13048890号