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

相关推荐
听说名字越长的就越牛逼25 分钟前
Mendix,在开发组件之前,需要了解的部分知识
前端·react.js·低代码
wordbaby26 分钟前
React 19 新特性:用 use 实现服务端和客户端组件的数据无缝协作
前端·react.js
wordbaby1 小时前
React 19 亮点:让异步请求和数据变更也能用 Transition 管理!
前端·react.js
工呈士2 小时前
Redux:不可变数据与纯函数的艺术
前端·react.js·面试
穗余13 小时前
NodeJS全栈开发面试题讲解——P5前端能力(React/Vue + API调用)
javascript·vue.js·react.js
早知道不学Java了14 小时前
chromedriver 下载失败
前端·vue.js·react.js·npm·node.js
哼唧唧_15 小时前
使用 React Native 开发鸿蒙运动健康类应用的高频易错点总结
react native·react.js·harmonyos·harmony os5·运动健康
EndingCoder16 小时前
React从基础入门到高级实战:React 高级主题 - React 微前端实践:构建可扩展的大型应用
前端·javascript·react.js·前端框架·状态模式
itslife18 小时前
fiber 节点与 FiberRootNode - HostRootFiber
前端·react.js
风迦叶18 小时前
React与Vue核心区别对比
前端·vue.js·人工智能·react.js