React中的hydrateRoot

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

在这些场景中使用:

  1. SSR(服务器端渲染)

    • HTML 是服务器返回的(例如使用 Next.js、Vike、Remix 等框架)。
    • 页面已包含渲染后的 HTML 和初始数据,客户端只负责水合。
  2. React 18 及以上版本

    • React 18 的 hydrateRoot 替代了 React 17 的 ReactDOM.hydrate()

⚠️ 与 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()windowdocument 等,仅在 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 控制台对象引用相等
客户端绑定了事件 点击按钮响应事件、控制台打印

相关推荐
jin1233221 小时前
基于React Native鸿蒙跨平台地址管理是许多电商、外卖、物流等应用的重要功能模块,实现了地址的添加、编辑、删除和设置默认等功能
javascript·react native·react.js·ecmascript·harmonyos
2501_920931701 小时前
React Native鸿蒙跨平台医疗健康类的血压记录,包括收缩压、舒张压、心率、日期、时间、备注和状态
javascript·react native·react.js·ecmascript·harmonyos
落霞的思绪2 小时前
配置React和React-dom为CDN引入
前端·react.js·前端框架
橙露2 小时前
React Hooks 深度解析:从基础使用到自定义 Hooks 的封装技巧
javascript·react.js·ecmascript
2501_920931702 小时前
React Native鸿蒙跨平台使用useState管理健康记录和过滤状态,支持多种健康数据类型(血压、体重等)并实现按类型过滤功能
javascript·react native·react.js·ecmascript·harmonyos
打小就很皮...3 小时前
dnd-kit 实现表格拖拽排序
前端·react.js·表格拖拽·dnd-kit
打小就很皮...3 小时前
React 19 + Vite 6 + SWC 构建优化实践
前端·react.js·vite·swc
Highcharts.js3 小时前
使用Highcharts与React集成 官网文档使用说明
前端·react.js·前端框架·react·highcharts·官方文档
摘星编程5 小时前
OpenHarmony + RN:Stack堆栈导航转场
react native·react.js·harmonyos
摘星编程5 小时前
OpenHarmony环境下React Native:Tooltip自动定位
javascript·react native·react.js