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 控制台对象引用相等
客户端绑定了事件 点击按钮响应事件、控制台打印

相关推荐
前端小盆友4 小时前
从零实现一个GPT 【React + Express】--- 【3】解析markdown,处理模型记忆
gpt·react.js
Cacciatore->6 小时前
React 基本介绍与项目创建
前端·react.js·arcgis
摸鱼仙人~6 小时前
React Ref 指南:原理、实现与实践
前端·javascript·react.js
贵沫末6 小时前
React——基础
前端·react.js·前端框架
爱学习的茄子6 小时前
AI驱动的单词学习应用:从图片识别到语音合成的完整实现
前端·深度学习·react.js
10年前端老司机7 小时前
在React项目中如何封装一个可扩展,复用性强的组件
前端·javascript·react.js
sophie旭7 小时前
《深入浅出react开发指南》总结之 10.1 React运行时总览
前端·react.js·源码阅读
轻语呢喃7 小时前
React智能前端:从零开始写的图片分析页面实战
前端·react.js·aigc
MiyueFE7 小时前
每个前端开发者都应该掌握的几个 ReactJS 概念
前端·react.js
旧时光_8 小时前
Zustand 状态管理库完全指南 - 进阶篇
前端·react.js