← 中文资源

简体中文版本

从源头修复 React 水合不匹配

读出服务器与客户端的确切差异,找到非确定性渲染输入,只在最后手段时才用 suppressHydrationWarning。

阅读英文原文

目标

水合就是拿服务器 HTML 与客户端首次渲染做逐节点比对并报告差异。每个差异都能追到一个非确定性输入:时间、随机数、区域设置或浏览器 API。修输入能让警告全线消失,压制警告只让控制台安静——布局 bug 照样上线。本食谱按“读差异、追输入、修源头、验证零压制”的顺序闭环。

步骤要点

  1. 读 React 报错里的 server/client 两侧片段,确认差异节点与差异内容(如时间戳秒级 vs 毫秒级)。
  2. 按频率排查四类源头:渲染期取当前时间、Math.random、环境差异、渲染期访问 window。
  3. 修法是推迟到挂载后而非压制:useState 占位加 useEffect 填真值加定时刷新,首次客户端渲染与服务器输出完全一致。
  4. 区域相关格式化要显式钉死 locale 保证两侧一致,产品需要用户本地化时挂载后再升级格式,suppress 只罩住单个文本节点。
  5. 验证:连刷三遍控制台零水合警告,rg 统计 suppressHydrationWarning 数量接近零且每处都有挂载后补丁的理由。

排错提示

只在生产出现的 mismatch 是 dev 双渲染掩盖或暴露了不同路径,用 curl 抓生产 HTML 配本地客户端构建精确复现;三方挂件在渲染期注入标记天然非确定,挪进 effect 的 ref 容器隔离到挂载后;非法 HTML 嵌套(div 塞进 p)会让浏览器重排节点引发连锁 mismatch,先按 React 错误码修嵌套,之前的后续差异都是噪音。

适用边界

suppressHydrationWarning 只静默一层文本与属性差异,救不了结构不匹配,且只应标记已知的挂载后补丁;确定性的首次渲染才是耐久契约,每接受一个分歧都是未来代码会效仿的永久例外。

代码示例

console error (React 18+):
  A tree hydrated but some attributes of the server-rendered HTML
   didn't match the client...
  - server: <span>synced at 14:03:11</span>
  - client: <span>synced at 14:03:11.412</span>   <-- new Date() at render

fix: render a stable placeholder, fill after mount
  server+client first paint: <span>synced at …</span>
  useEffect: setAt(new Date().toLocaleTimeString())

verification: reload x3, console clean,
  rg suppressHydrationWarning src/ -> 1 justified call