在 Next.js 中使用 localStorage

1,077 阅读1分钟

场景:

需要从 localStorage 中拿到某些元素的可见信息,例如顶部提示 noticeVisiable。

  1. 直接使用:
const [downloadNoticeVisiable, setDownloadNoticeVisiable] =useState(localStorage.getItem('download-notice-visiable') ? true : false);

报错:ReferenceError: localStorage is not defined.

  1. 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 将事件处理器附加到预渲染树的过程。

出现的原因:

  1. HTML 标签错误嵌套

    • p 标签中嵌套 p 标签
    • div 中嵌套 div
    • ul 或者 ol 中嵌套 p 标签
    • 响应式内容不能互相嵌套(a、button 等)
  2. 渲染逻辑中使用 typeof window !== 'undefined',即本文所见的情况

  3. 渲染逻辑中使用浏览器 api,window 或者 localStorage

  4. 浏览器插件修改了 HTML

  5. CSS-in-JS 库配置错误

  6. 尝试修改 HTML 响应的 Edge 或者 CDN 配置不正确

修复方式:

  1. 使用 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。

  1. 在某些组件禁用 SSR
import dynamic from 'next/dynamic'
 
const NoSSR = dynamic(() => import('../components/no-ssr'), { ssr: false })
 
export default function Page() {
  return (
    <div>
      <NoSSR />
    </div>
  )
}

禁用 SSR 可以后,在渲染该组件时,不会进行预渲染,自然也不会有水合差异。

  1. suppressHydrationWarning
<time datetime="2016-10-25" suppressHydrationWarning />

在某些元素上直接禁用水合不匹配的 warning。

本文采用了第一种解决方案:

  const [noticeHidden, setNoticeHidden] = useState(true);
  useEffect(() => {
    setNoticeHidden(Boolean(localStorage.getItem('notice.hidden')));
  }, []);

参考:

nextjs.org/docs/messag…