跳到主要内容

Hydration

Hydration(水合) 是现代前端框架(如 Next.js)在服务端渲染(SSR)或静态生成(SSG)场景下的激活过程

简单来说,它的作用是:将服务器生成的“静态”HTML 页面,变成浏览器中“动态”且可交互的 React 应用

如果没有 Hydration,你看到的只是一个不能点击、不能输入的“死”页面。


🌊 什么是 Hydration?

你可以把服务器返回的 HTML 想象成一个**“干燥的骨架”**。

  • 服务器:负责把肉(HTML 结构)和血(初始数据)填进去,快速展示给用户看(首屏快)。
  • Hydration:负责把“神经”(事件监听器 onClick/onChange)和“灵魂”(组件状态 useState/useEffect)注入进去,让页面“活”过来。

核心前提: Hydration 的过程不是删除旧的 DOM 重新渲染,而是复用服务器生成的 DOM 结构,仅仅在其上“绑定” JavaScript 逻辑。


🔄 Hydration 的完整流程

这是一个从用户敲回车到页面完全可用的过程:

  1. 服务端渲染 (SSR/SSG)

    • 服务器接收到请求。
    • 执行组件逻辑,获取数据,生成带有内容的纯 HTML 字符串。
    • 将 HTML 发送给浏览器。
  2. 浏览器展示

    • 浏览器收到 HTML,立即解析并显示内容(此时页面是静态的,点击按钮没反应)。
  3. 加载 JavaScript

    • 浏览器同时下载页面所需的 JavaScript 包(React 运行时、组件代码等)。
  4. 客户端水合 (Hydration)

    • JavaScript 执行,React 开始工作。
    • 对比 (Reconciliation):React 会生成一份“虚拟 DOM”(Virtual DOM),并将其与服务器发来的“真实 DOM”进行比对。
    • 绑定 (Binding):如果两者结构一致,React 会在现有的 DOM 节点上绑定事件监听器,并关联组件的状态。
    • 完成:页面变成完全可交互的 SPA(单页应用)。

🚀 App Router 中的进化:Partial Hydration (部分水合)

在 Next.js 的 App Router 中,Hydration 变得更智能了,不再需要“全量”水合。

传统模式 (全量水合):整个页面必须等所有 JS 加载完才能交互。 App Router 模式 (部分水合/岛屿架构)

  • 页面被拆分为 Server Components(服务端组件)和 Client Components(客户端组件)。
  • Server Components:永远不需要 Hydration(因为它们只在服务端运行)。
  • Client Components:只有这些“交互岛屿”需要 Hydration。

优势: 浏览器可以流式传输内容。先显示不需要交互的部分(如文章内容),后台悄悄加载交互组件(如评论框、轮播图)的 JS,等加载完再单独激活(Hydrate)那个小区域,极大提升了性能。


⚙️ 底层是如何实现的?

Hydration 的实现依赖于 React 对 DOM 的“接管”机制:

  1. 标记与序列化

    • 服务端渲染时,React 会给 DOM 节点添加特殊的 data-reactrootdata-reactid 等属性,标记这是由 React 管理的节点。
    • 对于 RSC(React Server Components),服务端还会发送一个 RSC Payload(一种特殊的 JSON 数据流),描述组件树的结构和数据。
  2. 客户端“注水”

    • 客户端 React 代码启动后,会寻找带有上述标记的 DOM 节点。
    • 它会根据 RSC Payload 或组件逻辑,在内存中重建虚拟 DOM 树。
    • 关键步骤:React 会遍历真实 DOM,将事件监听器附加到对应的节点上,并建立真实 DOM 与虚拟 DOM 的映射关系。
  3. 协调 (Reconcile)

    • React 确认客户端生成的虚拟 DOM 与服务端传来的 HTML 结构是否完全一致

⚠️ 常见坑:Hydration Mismatch (水合不匹配)

这是开发中最常见的错误,浏览器会报 Text content did not matchExpected server HTML to contain a matching...

原因: 服务器生成的 HTML 和 客户端第一次渲染的 Virtual DOM 结构不一致

  • 服务端:生成了 <div>加载中...</div>
  • 客户端:JS 加载后,立刻渲染了 <div>你好,用户</div>
  • 结果:React 想要复用 DOM,发现“加载中...”和“你好,用户”对不上,发生 Mismatch。

解决方案:

  1. 保持一致:确保服务端和客户端的初始渲染结果一致。
  2. 条件渲染延迟:涉及 windowdocumentlocalStorage 的代码(只在浏览器存在),应该放在 useEffect 里,或者使用 Suspense fallback。
  3. 使用 use client 明确边界:在 App Router 中,明确划分服务端和客户端组件,避免逻辑混淆。

总结

阶段状态作用
服务端渲染🏗️ 骨架搭建快速展示内容,利于 SEO
Hydration💧 注入灵魂绑定事件、恢复状态,让页面“活”过来
完成🏃‍♂️ 完全交互变成流畅的单页应用

Partial Hydration

Partial Hydration(部分水合)的工作原理可以形象地理解为**“按需激活”**。

在传统的全量水合(Full Hydration)中,浏览器必须等待页面所需的所有 JavaScript 代码都下载、解析完毕,才能一次性为整个页面的 DOM 绑定交互逻辑。这就像一座大楼,必须等所有装修材料(JS代码)都运到现场,工人才能开始工作,导致大楼在很长一段时间内都处于“不可用”状态。

而 Partial Hydration 则打破了这种“一刀切”的模式,它的核心流程如下:

1. 核心机制:选择性激活

Partial Hydration 的本质是将页面拆分为“静态部分”和“动态部分”。

  • 静态内容(Static Content):指那些不需要交互、内容相对固定的部分,例如博客文章的正文、新闻标题、商品描述等。

    • 处理方式:这些部分仅由服务器渲染成纯 HTML 发送给浏览器。浏览器显示它们,但不需要为其加载对应的 JavaScript 代码,也不需要进行水合。
    • 结果:它们就是纯粹的静态文档,加载极快,不消耗客户端计算资源。
  • 动态组件(Dynamic Components):指那些需要用户交互的部分,例如评论区、轮播图、购物车、表单按钮等。

    • 处理方式:这些组件被标记为“待水合”。浏览器会单独加载这些组件所需的 JavaScript 代码,并仅在这些特定的 DOM 节点上执行水合过程,绑定事件监听器和状态。
    • 结果:这些“交互岛屿”被“激活”,变得可点击、可操作。

2. 工作流程:静态先行,动态跟进

这是一个从服务器到浏览器的协作过程:

  1. 服务器拆分:服务器接收到请求后,会分析页面结构。它将页面拆解,把静态内容直接生成 HTML,而将动态组件(Client Components)标记为占位符,并附上一个“加载指令”。
  2. 流式传输:服务器立即将包含静态 HTML 和占位符的响应流式传输给浏览器。用户能立刻看到大部分内容(如文章),此时页面主体是静态的。
  3. 后台加载 JS:浏览器在展示静态内容的同时,在后台异步、优先加载动态组件所需的 JavaScript 包。
  4. 局部水合:一旦某个动态组件的 JS 代码加载完成,React 会立即找到对应的 DOM 占位符,执行水合,将其“激活”。这个过程是独立的,不影响页面其他部分。

3. 在 Next.js App Router 中的实现

在 Next.js App Router 中,Partial Hydration 是通过 React Server Components (RSC) 架构天然实现的。

  • Server Components:默认就是“静态”的。它们只在服务器运行,生成 HTML 后使命终结,永远不会在客户端进行水合。这极大地减少了需要传输和执行的 JavaScript 体积。
  • Client Components:只有这些组件需要水合。通过 use client 指令声明的组件,框架会知道需要为其生成并传输相应的客户端代码,并在合适时机执行局部水合。

4. 与传统全量水合的对比

特性全量水合 (Full Hydration)部分水合 (Partial Hydration)
JS 下载必须下载整个应用的 JS 包仅下载动态组件的 JS(按需)
激活时机所有 JS 加载完后,一次性激活整个页面静态内容立即可见,动态组件加载完即刻激活
资源消耗高(主线程被长时间阻塞)低(分块处理,减少主线程压力)
用户体验首屏可能出现白屏或长时间不可交互首屏极速展示,交互功能逐步可用

总结来说,Partial Hydration 通过只对页面中必要的交互部分进行“水合”,最大限度地减少了客户端的 JavaScript 负载和计算任务,从而实现了“静态内容即时可见,动态组件按需激活”的极致性能体验。