hydrateRoot
是 React 18 引入的一个 API,主要用于 客户端 React 应用的"水合"(hydration)过程。它用于将 React 应用"挂载"到由服务器预渲染好的 HTML 上,使得服务器和客户端的内容同步,从而实现真正的同构渲染(也称为 SSR,Server-Side Rendering)。
🧠 什么是 hydration(水合)?
水合指的是:
React 在客户端读取服务器返回的 HTML,然后"激活"这些 DOM 节点,让它们变成可以交互的 React 组件。
比如,一个页面由服务器返回了静态 HTML,React 不会重新渲染它,而是接管这些 DOM 节点并绑定事件、状态等逻辑。
🛠️ hydrateRoot
的作用
javascript
import { hydrateRoot } from 'react-dom/client';
import App from './App';
hydrateRoot(document.getElementById('root'), <App />);
它的作用和 ReactDOM.render()
类似,但专用于 SSR 场景:
- 检查现有 DOM 和 React 渲染结果是否一致(严格模式下会报错)。
- 保留服务器渲染好的 HTML,避免重新创建 DOM,提高性能。
- 绑定事件处理器、状态逻辑等 React 功能。
✅ 使用场景
hydrateRoot
应用于 SSR 项目的 客户端入口 文件:
javascript
// client.tsx or index.client.tsx
import { hydrateRoot } from 'react-dom/client';
import App from './App';
hydrateRoot(document.getElementById('root'), <App />);
在这些场景中使用:
-
SSR(服务器端渲染)
- HTML 是服务器返回的(例如使用 Next.js、Vike、Remix 等框架)。
- 页面已包含渲染后的 HTML 和初始数据,客户端只负责水合。
-
React 18 及以上版本
- React 18 的
hydrateRoot
替代了 React 17 的ReactDOM.hydrate()
。
- React 18 的
⚠️ 与 createRoot
的区别
API | 用途 | 使用场景 |
---|---|---|
createRoot |
普通 React 应用挂载 | SPA、CSR 应用 |
hydrateRoot |
水合已有的 HTML | SSR + 客户端交互激活 |
🧪 示例
服务端返回了 HTML:
xml
<div id="root">
<button>Click me</button>
</div>
客户端:
javascript
hydrateRoot(document.getElementById('root'), <button onClick={...}>Click me</button>);
这时候 React 不会重新渲染 <button>
,而是直接接管这个 DOM 并绑定事件。
✅ 服务端渲染(SSR)中使用 onClick={() => ...}
是合法的,但 不会被执行或包含在 HTML 中
📌 关键点:
React 在服务端渲染时:
- 会构建完整的虚拟 DOM
- 会忽略所有事件绑定函数(如
onClick
) - 只输出 HTML 标记(
<button>Click me</button>
),不会包含任何 JS 行为
所以:
scss
<button onClick={() => alert(data.message)}>Click me</button>
在服务端渲染的 HTML 中,只会生成:
css
<button>Click me</button>
事件处理器 onClick={() => alert(...)}
并不会在服务端执行、序列化或报错。
🧠 为什么不会报错?
因为:
- JSX 是静态的结构声明,JS 函数只是作为属性保存在虚拟 DOM 中;
- React 在服务端不会尝试"使用"这些函数,只是跳过;
- 所以无论是
onClick={...}
还是onMouseEnter={...}
,都不会报错,也不会出现在最终 HTML 中。
✅ 客户端 hydrateRoot
时会如何?
当客户端用如下代码:
javascript
hydrateRoot(document.getElementById('root'), <App data={data} />);
React 会:
- 读取现有的服务端 HTML(比如
<button>Click me</button>
) - 找到虚拟 DOM 中对应的
<button onClick=...>
- 将事件绑定到已有的按钮 DOM 上
- ✅ 这样就复用了 DOM,又绑定了事件,达成 hydration
❌ 错误理解(澄清)
❌ "服务端不能出现 onClick,会报错" ------ 不对,React 是安全处理的,不会执行,也不会输出
❌ "SSR 的 HTML 包含事件处理器" ------ 不对,只输出纯 HTML
✅ 补充:不能做什么?
虽然你可以写 onClick={...}
,但在 SSR 中不能执行副作用:
javascript
// ❌ 不要这样(服务端执行时会报错或行为异常)
<button onClick={() => {
console.log(window.location); // 服务端没有 window
}}>Click</button>
所以 事件处理器中引用的对象必须是客户端存在的 (如 alert()
、window
、document
等,仅在 hydrate 后生效)。
✅ 总结
问题 | 答案 |
---|---|
SSR 中可以写 onClick={...} 吗? |
✅ 可以,不会报错 |
SSR 渲染 HTML 会包含事件吗? | ❌ 不会,只有纯 HTML |
客户端 hydrate 会绑定事件吗? | ✅ 会,复用 DOM 并绑定事件 |
SSR 的事件处理器能访问 window 吗? |
❌ 不行,SSR 没有浏览器环境 |
验证案例
下面是一个可以完整运行的 Demo,用于对比:
- ✅ 服务端输出的 HTML(纯静态、无事件)
- ✅ 客户端
hydrateRoot
后 DOM 是否复用 - ✅ 事件是否成功绑定
🧪 目标验证点
验证内容 | 方法 |
---|---|
SSR 输出 HTML 是否含事件 | 查看服务端返回的 HTML |
Hydrate 后是否复用 DOM | 控制台对比节点引用 |
是否绑定事件 | 点击按钮触发事件,且控制台打印 |
✅ 最小可运行 Demo
1. App.jsx
javascript
export default function App({ data }) {
return (
<div>
<h1 data-ssr="true">SSR Hydration Demo</h1>
<button
data-ssr="true"
id="hydrate-test-button"
onClick={() => {
console.log('✅ Click event triggered!');
alert(data.message);
}}
>
Click me
</button>
</div>
);
}
2. 服务端渲染输出
在 entry-server.jsx
:
javascript
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './App';
export function render(url, data) {
const html = renderToString(<App data={data} />);
return { html };
}
访问页面后,你可以在浏览器"查看源码"(不是 Elements 面板)中看到:
ini
<h1 data-ssr="true">SSR Hydration Demo</h1>
<button data-ssr="true" id="hydrate-test-button">Click me</button>
✅ 没有 onClick
,说明服务端没输出事件。
3. 客户端入口 entry-client.jsx
javascript
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './App';
const data = window.__INITIAL_DATA__;
const beforeHydrateBtn = document.getElementById('hydrate-test-button');
console.log('💡 Before hydrate:', beforeHydrateBtn);
hydrateRoot(document.getElementById('root'), <App data={data} />);
setTimeout(() => {
const afterHydrateBtn = document.getElementById('hydrate-test-button');
console.log('💡 After hydrate (same node?):', beforeHydrateBtn === afterHydrateBtn);
}, 100);
4. 页面加载后控制台输出
bash
💡 Before hydrate: <button id="hydrate-test-button">Click me</button>
💡 After hydrate (same node?): true ✅
✅ 表示 DOM 是复用的。
5. 点击按钮测试
控制台输出:
csharp
✅ Click event triggered!
弹出 Hello from server!
✅ 表示客户端成功绑定事件。
✅ 小结验证成果
验证项 | 是否达成 | 方法 |
---|---|---|
服务端输出无事件处理器 | ✅ | 查看 HTML 源码(非 DevTools) |
客户端复用了按钮 DOM | ✅ | 控制台对象引用相等 |
客户端绑定了事件 | ✅ | 点击按钮响应事件、控制台打印 |