hydrateRoot
允许你将 React 组件显示在浏览器 DOM 节点中,该节点的 HTML 内容先前是由 react-dom/server
生成的。
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 节点”。这通常将是一段 JSX 代码,例如<App />
,它使用ReactDOM Server
方法(例如renderToPipeableStream(<App />)
)进行渲染。 -
可选
options
: 包含此 React 根的选项的对象。- 可选
onCaughtError
: 当 React 捕获错误边界中的错误时调用的回调函数。调用时会传入错误边界捕获的error
,以及包含componentStack
的errorInfo
对象。 - 可选
onUncaughtError
: 当抛出错误且未被错误边界捕获时调用的回调函数。调用时会传入抛出的error
,以及包含componentStack
的errorInfo
对象。 - 可选
onRecoverableError
:React 自动从错误中恢复时调用的回调函数。调用时会传入 React 抛出的error
对象,以及包含componentStack
的errorInfo
对象。一些可恢复的错误可能包含原始错误原因,作为error.cause
。 - 可选
identifierPrefix
:React 用于useId
生成的 ID 的字符串前缀。这在同一页面上使用多个根时,有助于避免冲突。必须与服务器上使用的前缀相同。
- 可选
返回
hydrateRoot
返回一个包含两个方法的对象:render
和 unmount
。
注意事项
hydrateRoot()
期望渲染的内容与服务器端渲染的内容完全一致。您应该将不匹配视为错误并修复它们。- 在开发模式下,React 会警告水合过程中的不匹配。对于不匹配情况,属性差异无法保证会被修复。出于性能原因,这非常重要,因为在大多数应用程序中,不匹配的情况很少见,因此验证所有标记的成本会非常高。
- 您的应用中可能只有一个
hydrateRoot
调用。如果您使用框架,它可能会为您执行此调用。 - 如果您的应用程序是客户端渲染的,并且没有预先渲染 HTML,则不支持使用
hydrateRoot()
。请改用createRoot()
。
root.render(reactNode)
调用 root.render
更新已水合的 React 根元素中浏览器 DOM 元素内的 React 组件。
root.render(<App />);
React 将更新已水合的 root
中的 <App />
。
参数
reactNode
:您想要更新的“React 节点”。这通常是类似<App />
的 JSX 代码片段,但您也可以传递使用createElement()
构造的 React 元素、字符串、数字、null
或undefined
。
返回
root.render
返回 undefined
。
注意事项
- 如果您在根元素完成水合之前调用
root.render
,React 将清除现有的服务器端渲染的 HTML 内容,并将整个根元素切换到客户端渲染。
root.unmount()
调用 root.unmount
销毁 React 根元素内的渲染树。
root.unmount();
完全使用 React 构建的应用程序通常不会调用 root.unmount
。
这主要在 React 根元素的 DOM 节点(或其任何祖先)可能被其他代码从 DOM 中移除的情况下有用。例如,假设一个 jQuery 选项卡面板从 DOM 中移除非活动选项卡。如果一个选项卡被移除,它内部的所有内容(包括内部的 React 根元素)也将从 DOM 中移除。您需要告诉 React 通过调用 root.unmount
来“停止”管理已移除根元素的内容。否则,已移除根元素内的组件将不会清理并释放订阅等资源。
调用 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 />);
这会将服务器中的 HTML 浏览器 DOM 节点 与应用程序的 React 组件 进行水合。通常,您只需在启动时执行一次。如果您使用框架,它可能会在后台为您完成此操作。
为了水合您的应用程序,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,您的组件将 使用状态。
水合整个文档
完全使用 React 构建的应用程序可以将整个文档(包括 <html>
标签)渲染为 JSX。
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> ); }
这样,初始渲染过程将渲染与服务器相同的内容,避免不匹配,但在水合后会立即进行额外的同步过程。
更新已水合的根组件
根组件水合完成后,您可以调用 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
并非常见。通常,您会 更新组件内部的状态。
显示未捕获错误的对话框
默认情况下,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 选项是一个函数,它带有两个参数。
- 抛出的 error。
- 一个 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 }); } } });
显示错误边界错误
默认情况下,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 选项是一个函数,它带有两个参数。
- 被边界捕获的 error。
- 一个 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
选项是一个带有两个参数的函数。
- React 抛出的 error 。有些错误可能包含原始原因,例如 error.cause。
- 一个 errorInfo 对象,其中包含错误的 componentStack。
您可以使用 onRecoverableError
根选项来显示水合不匹配的错误对话框。
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(...)
。
要修复此问题,请将根选项传递给 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});