先记住这个答案
SSR 输出的是 HTML 表示,不会把服务端内存中的函数闭包、事件处理器和组件运行状态直接传给浏览器。需要 React 交互的界面加载客户端代码后,通过 hydration 将组件逻辑与已有 DOM 对应起来,建立事件响应和后续状态更新。这样可以利用已经展示的节点,而不是从空容器重新创建。原生链接、普通表单和 details 等 HTML 能力并非都要等 hydration,所以应把“React 交互需要接管”与“页面完全不能交互”区分开。
- HTML 不会携带 JavaScript 函数闭包
- hydration 尝试复用已经存在的节点
- 原生浏览器行为与 React 事件不是同一层
服务器不能把点击函数塞进 HTML 就完成接管
一个计数按钮的服务端输出可以包含当前数字与按钮文字,但 onClick 引用的函数及其闭包不是普通 HTML 属性。浏览器需要拿到相应客户端代码,才能知道点击后如何更新状态、计算新输出并修改界面。
hydration 会让客户端组件与已有标记衔接起来,而不是简单地给每个 DOM 节点逐一补一个监听器。事件系统、组件状态和更新树都参与其中,把它等同于 addEventListener 会遗漏状态协调职责。
原生交互能否工作取决于 HTML 本身
一个带 href 的链接可以由浏览器直接导航,具有 action 和 method 的普通表单也能按浏览器规则提交。反之,只有 React onClick 才定义行为的按钮,在对应逻辑接管前没有那份业务处理。
因此渐进增强可以为关键路径提供可用的 HTML 基础,再叠加客户端体验。但框架表单、客户端路由拦截和业务验证各有具体行为,不能仅凭标签叫 form 就断言任何复杂流程都已具备无脚本能力。
复用已有 DOM 需要一致的首次输出
浏览器已经显示了服务端内容,如果客户端初次渲染突然使用另一份数据或结构,React 就难以可靠对应节点。应把同一份初始数据交给两端,避免随机数、环境分支或时区差异改变首次界面。
直接用客户端根重建已有 SSR 内容,会放弃原节点复用,可能影响焦点、用户已经输入的内容和页面稳定性。hydration 本身仍有执行成本,保留 HTML 快照并不意味着客户端无需渲染计算或一定瞬间完成。
容易答错的地方
- 宣称 hydration 前页面完全不能交互
- 这种说法忽略了浏览器原生链接、表单和折叠元素等能力。应明确某个操作依赖原生 HTML 还是 React 处理器,再决定是否需要禁用、加载提示或渐进增强,避免给页面增加不必要的等待。
- 认为服务端状态对象被原样搬到客户端
- 两端运行环境与内存空间不同,需要传递可用的初始数据并由客户端建立对应运行状态。闭包、连接和任意实例不能仅靠输出 HTML 自动共享,序列化和资源生命周期必须单独设计。
面试官还会怎么问?
没有任何客户端交互还需要 hydration 吗?
纯静态内容可以只发送 HTML,不必为了阅读文本而加载 React 运行时。具体框架是否仍发送脚本要看实现与配置,架构上应根据交互需求划定客户端范围,而不是默认所有正文都要接管。
为什么要区分可见时间和可交互时间?
服务端正文到达时用户可能已经看到按钮,但其 React 逻辑还在下载或执行。若只测内容绘制,会漏掉点击等待;应结合关键操作验证实际响应,并避免让用户误以为操作已提交成功。
SSR 与 React Server Components 是一回事吗?
不是。SSR 主要讨论初始 HTML 的生成,Server Components 则涉及组件在哪里执行以及向客户端传递什么。它们可以组合,客户端交互边界仍需要相应代码,不能用其中一个概念替代另一个。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。