hydrateRoot 可在由 react-dom/server 预生成的 HTML 内容的浏览器 DOM 节点内部显示 React 组件。

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

参考

hydrateRoot(domNode, reactNode, options?)

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

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

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

React 会连接到在 domNode 中的 HTML,并负责管理里面的 DOM。完全用 React 构建的应用程序通常只有一个 hydrateRoot 调用,其中包含其根组件。

在下面查看更多示例。

参数

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

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

  • 可选 options: 一个对象,其中包含此 React 根的选项。

    • Canary only 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 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
    • 可选 identifierPrefix:一个字符串前缀,用于 React 为 useId. 生成的 ID。当在同一页面上使用多个根时,此选项非常适用于避免冲突。必须与服务器上使用的前缀相同。

返回

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

注意事项

  • hydrateRoot() 期望呈现的内容与服务器呈现的内容相同。出现不匹配的情况应视为错误并加以修复。
  • 在开发模式下,React 会在水合期间针对不匹配的情况发出警告。无法保证在出现不匹配时会修补属性差异。由于在大多数应用中不匹配情况都较少见,因此出于性能方面的考虑,验证所有标记的成本非常高昂,因而上述行为非常重要。
  • 在你的应用中可能只调用了一次 hydrateRoot。如果你使用框架,该框架可能会代你执行此调用。
  • 如果您的应用程序是客户端渲染的,并且还没有渲染任何 HTML,则不支持使用 hydrateRoot()。使用 createRoot() 代替。

root.render(reactNode)

调用 root.render 更新浏览器 DOM 元素的已加载的 React 根中的 React 组件。

root.render(<App />);

React 将在已加载的 root 中更新 <App />

在下面查看更多示例。

参数

  • reactNode:一个你要更新的“React 节点”。通常是类似于 <App /> 的 JSX 代码段,但你也可以传递使用 createElement() 构建的 React 元素、一个字符串、一个数字、nullundefined

返回

root.render 返回 undefined

注意事项

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

root.unmount()

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

root.unmount();

完全使用 React 构建的应用通常不需要调用 root.unmount

当 React 根的 DOM 节点(或它的任何祖代节点)被其他代码从 DOM 中移除时,这个方法非常有用。例如,设想一个 jQuery 标签面板,它从 DOM 中移除了非活动标签。如果一个标签被移除,它里面的所有内容(包括里面的 React 根节点)也会从 DOM 中移除。你需要通过调用 root.unmount 通知 React “停止”管理被移除根节点的内容。否则,被移除节点中的组件将不会清理和释放订阅等资源。

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

参数

root.unmount 不接受任何参数。

返回

root.unmount 返回 undefined

警告

  • 调用 root.unmount 会卸载树中的所有组件,并将 React 从根 DOM 节点“分离”。

  • 一旦你调用 root.unmount,你就无法再对根调用 root.render。试图对已卸载的根调用 root.render 会抛出“无法更新已卸载的根”错误。


使用方法

为服务器渲染的 HTML 进行水化

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

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

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

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

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

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

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

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

陷阱

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

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

导致 hydration 错误的最常见原因包括:

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

React 会修复部分水化错误,但您必须像修复其他 bug 那样修复这些错误。在最理想的情况下,这些错误会导致应用运行速度变慢;而在最糟糕的情况下,事件处理程序可能会依附到错误的元素上。


水化整个文档

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

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

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

hydrateRoot(document, <App />);

抑制不可避免的水合失配错误

如果服务器和客户端的单个元素属性或文本的内容不可避免地不同(例如,时间戳),则可以选择忽略水合失配警告。

若要忽略元素上的水合警告,请添加 suppressHydrationWarning={true}

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

这只能有效应用于一个层级,并且是一个逃生舱口。不要过度使用。除非是文本内容,否则 React 仍然不会尝试修补它,因此它可能在将来更新之前一直保持不一致。


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

如果需要在服务器和客户端上故意渲染不同的内容,可以使用双重渲染。在客户端上渲染不同内容的组件可以读取一个 状态变量,比如 isClient,这个变量可以在 Effect 中设置为 true

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

这样,最初的渲染过程会与服务器渲染相同的内容,避免出现不匹配的情况。但是,在水合之后紧接着就会立即进行额外的渲染。

陷阱

这种方法会让水合变得更慢,因为组件需要渲染两次。注意慢连接下用户的体验。JavaScript 代码加载的时间可能远晚于最初的 HTML 渲染,所以在水合后立即渲染不同的 UI 也可能让用户感到突兀。


更新已水合的根组件

根完成水合后,你可以调用root.render来更新根 React 组件。这与createRoot不同,通常不需要这样做,因为初始内容已经作为 HTML 呈现。

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

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。通常,你将更新其中一个组件内部的状态

针对未捕获错误显示一个对话框

Canary

onUncaughtError仅在最新的 React Canary 版本中提供。

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

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 选项是一个使用两个参数调用的函数

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

您可以使用 onUncaughtError 根选项来显示错误对话框

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

显示错误边界错误

Canary

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

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

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 选项是一个使用两个参数调用的函数

  1. error 是边界捕获的错误。
  2. 包含错误 errorInfo 对象,该对象包含 componentStack

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

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

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

当 React 遇到水化不匹配时,它会通过在客户端呈现来自动尝试恢复。默认情况下,React 会将水化不匹配错误记录到 console.error 中。若要替代此行为,你可以提供可选的 onRecoverableError 根选项

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 选项是一个使用两个参数调用的函数

  1. React 抛出的 error。部分错误可能包含原始原因为 error.cause
  2. 包含错误 componentStackerrorInfo 对象。

您可以使用 onRecoverableError 根选项来显示 hydration 不匹配的错误对话框

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

排查问题

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

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

控制台
警告:向 root.render(…) 传递了第二个参数,但它仅接受一个参数。

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

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

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