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 theerror
caught by the Error Boundary, and anerrorInfo
object containing thecomponentStack
. - Canary only optional
onUncaughtError
: Callback called when an error is thrown and not caught by an Error Boundary. Called with theerror
that was thrown and anerrorInfo
object containing thecomponentStack
. - 可选
onRecoverableError
:当 React 自动从错误中恢复时调用的回调。使用 React 抛出的error
和一个包含componentStack
的errorInfo
对象来调用。某些可恢复的错误可能将原始错误原因包含为error.cause
。 - 可选
identifierPrefix
:一个字符串前缀,用于 React 为useId
. 生成的 ID。当在同一页面上使用多个根时,此选项非常适用于避免冲突。必须与服务器上使用的前缀相同。
- Canary only optional
返回
hydrateRoot
返回一个包含两个方法的对象:render
和 unmount
.
注意事项
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 元素、一个字符串、一个数字、null
或undefined
。
返回
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,你的组件将会 使用状态。
水化整个文档
完全使用 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> ); }
这样,最初的渲染过程会与服务器渲染相同的内容,避免出现不匹配的情况。但是,在水合之后紧接着就会立即进行额外的渲染。
更新已水合的根组件
根完成水合后,你可以调用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。
- 包含错误 componentStack 的 errorInfo 对象。
您可以使用 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 选项传给 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});