什么是 Hydration
Hydrate 是水合物的意思,指的是含有水的化合物。
水合反应(Hydration Reaction),也叫作水化,是一种化学反应,其中物质与水结合。
Web 开发中,Hydration 是指客户端 js 通过绑定事件处理将静态的 HTML 转为动态网页的过程。在 SSR 中,HTML 在服务端预渲染,在客户端绑定事件处理程序之前还不能交互。
前端开发中,Hydration 有多种叫法。有的叫水合,有的是水化;Vue 官网 vuejs.org/guide/scali… 中文翻译为客户端激活;有的分为注水、脱水,注水符合,但是脱水是指服务端生成 HTML,这里并没有脱水的概念,后面会再解释。我觉得叫水合或者水化就好,以下统称为水合。
以上是水合的基本的定义以及在前端中的概念。但是还是不好理解,为什么已经有服务端生成的 HTML,还需要客户端激活呢?为什么以前没有这个概念?要搞清楚这个问题,就要说到 SSR。SSR 我们能够理解,以前都是 SSR,为什么没有水合呢?
什么是 SSR
传统 SSR
在前后端分离之前,网站的开发技术使用 Java、PHP 等,开发流程大概是前端写静态页面(HTML + CSS)、一些 js 特效,服务端拿去嵌入动态内容。当浏览器请求页面地址时,服务端会生成具体的页面内容,用户浏览器打开页面,加载 HTML(HTML 内容在服务端已经准备好了)、加载 js(负责一些交互效果)。此时是重后端轻前端。
那个时候并没有所谓服务端渲染的说法,但它确实是 SSR。
CSR
到了前端分离时代,单页面应用(SPA)兴起,服务端不再过问前端 HTML,不再插手前端的交互。前端有独立的工程,全权负责页面的交互,单页面控制路由跳转。此时服务端只是通过接口提供数据。
用户浏览器打开页面时,加载 HTML,加载 js,但是其中的 HTML 并没有任何具体的内容,只有等 js 加载完成,代码执行才会生成页面内容。此时是重前端。
CSR 问题
- 白屏问题:因为 CSR 会把整个网站打包进 js 里,只有加载了 js 才能显示页面内容,所以会影响网页的 LCP,在加载完 js 之前,会有一个白屏的时间。当 js 体积较大或者渲染较为复杂的时候,白屏较为明显。
- SEO:因为页面是动态生成的,HTML 源码中没有实际内容,导致搜索引擎爬虫无法进行内容抓取,所以不利于 SEO。
当然,以上两点都可以进行不同方面的优化和解决。
现代 SSR
讨论更多的是现在的 SSR,这是针对 CSR 问题提出的一种解决方案。从实现和实际的角度出发,我们讨论的应该是 Next 或者 Nuxt 的 SSR,或者 Vite 的 SSR 方案。
在 CSR 的基础上,如果增加一个服务端(Server),那不就解决了 CSR 的问题了吗?服务端负责渲染生成 HTML。
用户浏览器打开页面,前端的服务端根据路由获取数据,填充 HTML,将有内容的 HTML 以及 js 发送给浏览器,现在的 HTML 源码中就有了内容。但仅仅有内容,还不能交互,因为事件绑定、交互相关的逻辑还是由客户端 js 控制。客户端 js 使 HTML 能够交互的过程就是上面所说的 Hydration。
Hydration 过程做了什么?
- 绑定事件处理器:使得服务器端渲染的 HTML 元素变得可交互。比如,按钮的点击事件、表单的提交事件等。
- 重建应用状态:恢复或初始化客户端的 JavaScript 应用状态,使得客户端代码和服务器端渲染的一致。
- 同步 DOM:确保在客户端的 React(或其他前端框架)的虚拟 DOM(Virtual DOM)与服务器端生成的实际 DOM 一致。
为什么传统的 SSR 没有 Hydration 的概念?
这样看来,传统 SSR 服务器发送给浏览器 HTML、js,js 加载执行的过程也相当于水合,只是传统 SSR 是多页面,前端并没有复杂的状态管理,服务器发送的 HTML 基本上可以直接交互。
-
现代前端框架的组件化和状态管理:
- 现代前端框架(如 React、Vue)是基于组件化和状态管理的。每个组件有自己的生命周期和状态,这些都需要在客户端重新初始化,以便组件可以响应用户的交互。
- 传统的 SSR 更倾向于在服务器端完成所有动态内容的生成,客户端主要负责一些基本的交互和效果,并不需要对整个应用进行状态管理和重新渲染。
-
虚拟 DOM 和客户端渲染:
- 现代前端框架使用虚拟 DOM 来优化渲染过程。Hydration 需要将虚拟 DOM 和服务器生成的实际 DOM 对齐,以确保两者一致。
- 传统的 SSR 没有虚拟 DOM 概念,因此不需要这一步。
-
单页应用(SPA)与多页应用(MPA) :
- 现代的前端框架通常用于构建单页应用(SPA),需要在客户端执行大量逻辑,包括路由、状态管理等。Hydration 使得在初次加载后,客户端能够接管整个应用的渲染和状态管理。
- 传统的 SSR 更常用于多页应用(MPA),每次页面请求都重新加载整个页面,因此不需要在客户端保存和管理复杂的应用状态。
为什么叫 Hydration
Hydration 这个单词在化学中的本义是指某物与水结合,形成一种新的物质或状态。这个过程通常意味着将某种干燥或静态的物质与水结合,使其变得活跃或恢复其功能。例如,干燥的植物细胞在吸收水分后会恢复生机。
在前端开发中,使用 Hydration 这个单词有类似的隐喻意义:
-
静态与动态的转变:
- 化学中:干燥的物质通过吸收水分变得活跃。
- 前端开发中:服务器生成的静态 HTML 在浏览器中通过客户端 JavaScript 的 “激活” 变得动态和可交互。
-
恢复功能或状态:
- 化学中:水合作用恢复了细胞或物质的功能。
- 前端开发中:Hydration 过程将静态 HTML 恢复为一个功能齐全的 React 应用,恢复其交互性和动态性。
Hydration 只存在于现代前端框架 SSR 场景中吗?
虽然 Hydration 这个术语在现代前端框架得到了广泛使用,特别是在 SSR(服务器端渲染)场景下,但其实它的概念和逻辑并不完全局限于此。在某些更广义的场景中,也可以找到类似的过程和机制。
1. 现代前端框架中的 Hydration
在 React 和 Vue 等现代前端框架中,Hydrate 通常用于描述将服务器端生成的静态 HTML 内容转换为动态、可交互的页面的过程。这种做法结合了 SSR 和 CSR(客户端渲染)的优点:
- 服务器端渲染(SSR) 提供了更快的首屏加载时间和更好的 SEO。
- 客户端渲染(CSR) 提供了更丰富的交互体验。
通过 Hydration,页面可以在初次加载时快速展示内容,并且在加载完客户端 JavaScript 后变得动态和可交互。
2. 传统 Web 开发中的类似概念
在传统的 Web 开发(例如使用 PHP、ASP.NET、JSP 等技术)中,虽然没有明确使用 Hydration 这个术语,但确实存在类似的过程:
- 服务器端生成 HTML:服务器端生成完整的 HTML 页面,并将其发送到客户端。
- 客户端 JavaScript:在客户端加载 JavaScript 文件,用于添加交互行为,如事件监听、动态内容更新等。
3. 其他场景中的 Hydration 概念
除了传统的 Web 开发和现代前端框架外,在一些其他场景中也存在类似的 Hydration 过程:
- 静态站点生成(SSG) :在静态站点生成器(如 Gatsby、Next.js 的静态导出模式)中,初始页面会在构建时生成静态 HTML。客户端加载后,这些静态内容通过 JavaScript 变得动态和可交互。
- 增量静态渲染(ISR) :某些现代框架(如 Next.js 提供的 ISR)允许静态页面在构建后的特定时间内动态更新,而客户端加载的过程中依然需要进行 Hydration。
Hydration 中的挑战
- 一致性问题:如果服务器端生成的 HTML 和客户端的虚拟 DOM 不一致,可能会导致 UI 异常或性能问题。例如,某些状态在服务器和客户端之间可能会不一致,从而导致不必要的重渲染。
- 客户端和服务器端渲染差异:在一些情况下,客户端和服务器端渲染的结果可能不同,例如由于随机数、时间戳、浏览器特定的行为等。开发者需要确保这些差异最小化。
- 初始加载的 JavaScript 体积:尽管 Hydrate 优化了首屏加载时间,但客户端仍需下载和执行相当一部分 JavaScript 代码。保持 JavaScript 代码的轻量和高效是非常重要的。
为什么不是和传统 SSR 一样,在服务端就做好绑定事件等所有逻辑?
为什么现代前端框架在 SSR 时不在服务器端完成所有逻辑,而是选择通过 hydrate 在客户端完成部分逻辑的原因:
1. 客户端交互性需求
现代 Web 应用通常拥有丰富的交互性和复杂的状态管理。这些交互和状态管理需要在客户端进行,因为:
- 实时用户操作:用户在浏览器中进行的各种操作(如点击、表单输入等)需要实时反馈,服务器端无法实时感知这些操作并做出响应。
- 高频率的状态更新:某些应用场景(如实时聊天、动态数据展示等)需要频繁更新状态,完全依赖服务器端渲染会导致较高的延迟和性能瓶颈。
2. 服务器负载
如果所有的逻辑,包括事件处理和状态管理,都在服务器端完成,将导致服务器的负载显著增加。这会带来以下问题:
- 扩展性:服务器在处理每个请求时都需要完成复杂的渲染和逻辑计算,难以水平扩展。
- 性能瓶颈:服务器需要处理大量的计算和状态管理,容易成为性能瓶颈,特别是在高并发场景下。
3. 客户端状态管理
在现代前端框架中,状态管理是一项非常重要的任务,这通常需要在客户端完成:
- 保持客户端状态:某些状态是特定于用户会话的,必须在客户端保存和管理,例如用户偏好、表单输入、临时缓存等。
- 即时反馈:客户端渲染可以提供更即时的用户反馈,如动画、过度效果等,而这些在服务器端完成是行不通的。
4. 代码重用和开发效率
通过将渲染逻辑和事件绑定分离到客户端,可以更好地实现代码复用和提高开发效率:
- 组件化:现代框架通常采用组件化的开发方式,组件可以在服务器端渲染(SSR)和客户端渲染(CSR)中重用。
- 一致性:客户端和服务器端共享相同的组件和状态逻辑,有助于保持应用的一致性和减少重复代码。
部署的不同
部署的时候,CSR 只需要将静态资源上传到服务器,SSR 需要启动 node 的服务,所以在使用 Next 时,build 完还需要 start 启动 Server。
Next.js 提供了一个后端环境用于生成服务器端渲染(SSR)的 HTML。通过这种方式,Next.js 能够在服务端生成页面的初始 HTML,并将其发送给客户端,从而实现更快的首屏加载时间和更好的搜索引擎优化(SEO)。
同构
同构这个词有点奇怪,起源于 2013 年 Airbnb 提出的概念,但是这个说法有争议(什么是前端的同构渲染? - 林一二的回答 - 知乎 www.zhihu.com/question/32… 我建议不要深入这个概念,理解 SSR 即可。
现在对同构的理解一般是指:同一套代码,既能在服务端运行又能在客户端运行。但基本上还是上面介绍的现代 SSR(SSR + CSR)的概念。