React中Hydration是什么?求其工作原理及hydration error解决方法
一、Hydration 是什么?它的工作原理
Hydration(水合)是 React 服务端渲染(SSR)或静态站点生成(SSG)流程中的核心步骤,目的是把服务端输出的静态 HTML 转换成可交互的 React 应用,具体流程如下:
第一步:服务端生成静态 HTML
服务端会将 React 组件编译成完整的 HTML 字符串(比如 Next.js 中通过getServerSideProps/getStaticProps预取数据后渲染组件),然后把这个 HTML 发送给浏览器。此时用户看到的是静态页面,没有任何交互能力。第二步:浏览器加载并显示静态内容
浏览器接收到 HTML 后会快速解析并渲染,用户能立刻看到页面内容,这也是 SSR/SSG 提升首屏体验的关键。第三步:React 客户端代码启动并对比 DOM
浏览器加载完 React 客户端代码后,会生成对应的虚拟 DOM,然后和服务端输出的真实 DOM 结构做严格对比。第四步:完成 Hydration,激活交互
当虚拟 DOM 和真实 DOM 完全匹配时,React 会给 DOM 元素绑定事件处理器,激活组件的状态管理逻辑,静态页面就变成了可交互的 React 应用。
核心原则:服务端输出的 HTML 结构必须和客户端首次渲染的虚拟 DOM 结构完全一致,否则就会触发 hydration error。
二、常见 hydration error 原因及解决方法
hydration error 的本质是「服务端 HTML」和「客户端首次渲染的 DOM」不匹配,以下是最常见的场景和解决方式:
1. 浏览器专属 API 在服务端被调用
服务端没有 window、document 等浏览器专属对象,如果组件在初始化阶段(比如 render 函数、useState 初始化、组件顶层代码)直接访问这些对象,会导致服务端和客户端渲染的内容不一致。
错误示例:
function Header() { // 服务端渲染时会报错,且客户端渲染的内容和服务端不一致 const isMobile = window.innerWidth < 768; return <div>{isMobile ? '移动端导航' : '桌面端导航'}</div>; }
解决方法:
把浏览器专属逻辑放到 useEffect 中,确保只在客户端 Hydration 完成后执行:
function Header() { const [isMobile, setIsMobile] = useState(false); useEffect(() => { setIsMobile(window.innerWidth < 768); // 可选:监听窗口大小变化 const handleResize = () => setIsMobile(window.innerWidth < 768); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return <div>{isMobile ? '移动端导航' : '桌面端导航'}</div>; }
2. 服务端与客户端数据不一致
如果服务端渲染时使用的数据和客户端首次渲染时的数据不同,会直接导致 DOM 结构差异。比如服务端从数据库取到的用户信息,和客户端从本地缓存取到的不一致。
解决方法:
- 确保服务端和客户端使用同一数据源获取数据;
- 在 SSR/SSG 流程中,将服务端获取的数据通过 props 传递给客户端,避免客户端重复获取不同数据;
- 如果必须在客户端获取数据,在数据加载完成前,渲染和服务端一致的占位符(比如 loading 骨架屏)。
3. 条件渲染逻辑不一致
服务端和客户端的条件判断逻辑不同,会导致渲染的 DOM 结构不一样。比如服务端判断用户登录状态的逻辑依赖后端接口,而客户端依赖 localStorage,两者结果不同。
解决方法:
- 统一服务端和客户端的条件判断逻辑;
- 如果条件依赖客户端专属状态(比如
localStorage、sessionStorage),先在服务端渲染默认状态的内容,再通过useEffect在 Hydration 完成后更新组件状态:
function UserProfile() { const [user, setUser] = useState(null); useEffect(() => { // 客户端从 localStorage 获取用户信息 const savedUser = localStorage.getItem('user'); if (savedUser) setUser(JSON.parse(savedUser)); }, []); // 服务端和客户端首次渲染都显示占位符 if (!user) return <div>加载中...</div>; return <div>欢迎,{user.name}</div>; }
4. 第三方组件不支持 SSR
部分第三方 UI 组件没有做 SSR 适配,服务端渲染时输出的 HTML 和客户端渲染的不一致。
解决方法:
- 查看组件文档,确认是否支持 SSR,按照文档配置;
- 在 Next.js 等框架中,用动态导入并禁用 SSR,让组件只在客户端渲染:
import dynamic from 'next/dynamic'; const NonSSRComponent = dynamic(() => import('../components/NonSSRComponent'), { ssr: false, loading: () => <div>加载中...</div> });
5. DOM 属性或标签名不匹配
比如服务端输出的 HTML 中用了错误的属性名(比如手动写 class 而非 React 标准的 className),或者标签名大小写不一致(比如服务端输出 <div>,客户端渲染 <DIV>),都会导致对比失败。
解决方法:
- 始终使用 React 标准的属性名(比如
className代替class,htmlFor代替for); - 确保标签名统一使用小写,避免大小写差异。
调试技巧
浏览器控制台的 hydration error 信息会明确指出不匹配的 DOM 元素位置,根据提示定位到对应的组件,逐一排查上述场景即可。
内容的提问来源于stack exchange,提问作者himanshu_942

