场景:
需要从 localStorage 中拿到某些元素的可见信息,例如顶部提示 noticeVisiable。
- 直接使用:
const [downloadNoticeVisiable, setDownloadNoticeVisiable] =useState(localStorage.getItem('download-notice-visiable') ? true : false);
报错:ReferenceError: localStorage is not defined.
- check window 是否存在
const [downloadNoticeVisiable, setDownloadNoticeVisiable] = useState(
typeof window !== 'undefined' &&
window.localStorage &&
localStorage.getItem('download-notice-visiable')
? true
: false,
);
报错:Error: Hydration failed because the initial UI does not match what was rendered on the server.
查看 next 文档发现:是由于 Hydration(水合)造成的。next 解释为:服务器预渲染的 React 树和浏览器实际渲染的 React 树有差异造成的,而 Hydration 则是 React 将事件处理器附加到预渲染树的过程。
出现的原因:
-
HTML 标签错误嵌套
- p 标签中嵌套 p 标签
- div 中嵌套 div
- ul 或者 ol 中嵌套 p 标签
- 响应式内容不能互相嵌套(a、button 等)
-
渲染逻辑中使用
typeof window !== 'undefined',即本文所见的情况 -
渲染逻辑中使用浏览器 api,
window或者localStorage -
浏览器插件修改了 HTML
-
CSS-in-JS 库配置错误
-
尝试修改 HTML 响应的 Edge 或者 CDN 配置不正确
修复方式:
- 使用
useEffect
import { useState, useEffect } from 'react'
export default function App() {
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
return <h1>{isClient ? 'This is never prerendered' : 'Prerendered'}</h1>
}
在水合阶段,useEffect 被调用,这时候可以调用浏览器 api。
- 在某些组件禁用 SSR
import dynamic from 'next/dynamic'
const NoSSR = dynamic(() => import('../components/no-ssr'), { ssr: false })
export default function Page() {
return (
<div>
<NoSSR />
</div>
)
}
禁用 SSR 可以后,在渲染该组件时,不会进行预渲染,自然也不会有水合差异。
suppressHydrationWarning
<time datetime="2016-10-25" suppressHydrationWarning />
在某些元素上直接禁用水合不匹配的 warning。
本文采用了第一种解决方案:
const [noticeHidden, setNoticeHidden] = useState(true);
useEffect(() => {
setNoticeHidden(Boolean(localStorage.getItem('notice.hidden')));
}, []);