解决Next.js服务端渲染时出现Hydration(水合)错误

3,930 阅读2分钟

前言:

官话:While rendering your application, there was a difference between the React tree that was pre-rendered (SSR/SSG) and the React tree that rendered during the first render in the Browser. The first render is called Hydration which is a feature of React.

翻译:在渲染你的应用程序时,预先渲染的React树(SSR/SSG)和在浏览器中第一次渲染时渲染的React树之间存在差异。第一次渲染被称为Hydration,这是React的一个特性。

人话:大概意思就是在使用SSG或者SSR服务端渲染的时候,服务端渲染的页面跟当前第一次客户端渲染的页面存在差异(个人理解,有误大家可以帮忙纠错)

在React18之前,虽然会有类似的情况出现,但是不至于出现报错,只是在控制台输出警告⚠️,可是在更新React18之后,React官方将这个警告⚠️提升为了一个错误❌

就如同下面的错误❌

image-20230228162425858

解决

排查在哪里出现

如果是之前都没有产生这个错误,在dom中添加了某个数据后就出现了错误,那就是那个数据产生了水合反应

如果是跟我一样更换了某个库,这个库覆盖了整个项目,更换之后就出现了这个水合问题,很头痛,我就只能把项目分成三部分,把某一部分注释后看会不会报错,这样就快速排查出了是侧边导航栏出现这个水合问题

image-20230228162829620

解决这个错误

1.因为使用不规范HTML标签导致

//水合错误的例子
export const IncorrectComponent = () => {
  return (
    <p>
      <div>
        This is not correct and should never be done because the p tag has been
        abused
      </div>
      <Image src="/vercel.svg" alt="" width="30" height="30" />
    </p>
  )
}
//正确解决办法
export const CorrectComponent = () => {
  return (
    <div>
      <div>
        This is correct and should work because a div is really good for this
        task.
      </div>
      <Image src="/vercel.svg" alt="" width="30" height="30" />
    </div>
  )
}

2.数据问题导致水合

有两种办法,其实都大同小异

第一种:

//错误情况
function MyComponent() {
  const color = typeof window !== 'undefined' ? 'red' : 'blue'
  return <h1 className={`title ${color}`}>Hello World!</h1>
}
//上述情况在服务端渲染的时候Hell World是蓝色的,当在客户端第一次渲染的时候是红色的,这就会出现水合错误
​
​
//解决办法
function MyComponent() {
  const [color,setColor] = useState<string>('blue')
  useEffect(()=>{
    setColor("red")
  },[])
  const color = typeof window !== 'undefined' ? 'red' : 'blue'
  return <h1 className={`title ${color}`}>Hello World!</h1>
}

第二种:

定义一个HookuseMounted

import React, { useEffect, useState } from 'react';
​
const useMounted = () => {
    const [isMounted, setIsMounted] = useState<boolean>(false)
    useEffect(() => {
        setIsMounted(true)
    }, [])
​
    return isMounted;
}
​
export default useMounted;

在出现错误的地方使用

const userInfo = ()=>{
  const isMounted = useMounted();
  return (
    {isMounted && <div>当前用户余额:{data?.formatted}{data?.symbol}</div>}
  )
}

一个简单的记录📝,虽然写的很随意,但是也希望能帮助到需要的人