Nextjs: Hydration failed because the initial UI does not mat

这是Reactv18开始有的问题,官方描述:

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。

我的理解是:

因为Nextjs启动时会同时产生Server Side与Client Side (两个都包含React tree),Server端React tree 是在 pre-rendered (SSR/SSG) 产生的,而Client端React Tree是first render in the Browser (又叫做Hydration),当两者认知的React Tree不一样时就会发生此错误!!!
(不一样会导致 React Tree与 DOM 不同步,并导致出现意外的内容/属性。)

这个问题对应的情境其实有好几个,我先列我遇到的:

情境1: 有动态更新资料的Component

Whenever you are creating a component that has dynamic data. Try importing that component in below mentioned format. It disables SSR and lets you render new data on API call.
(意思是如果该Component需要动态捞资料,则透过dymanic关闭SSR渲染达到动态目的,避免Server端一开始先渲染导致与Client不一样而冲突)

//===============React18版本会出错==============//import ProductCard from "./Product"; //这个Component会动态去载入图片//================可以运作的方法=================//// Whenever you are creating a component that has dynamic data. Try importing that component in below mentioned format. // It disables SSR and lets you render new data on API call.import dynamic from 'next/dynamic';const ProductCard = dynamic(() => import("./Product"), {    ssr: false,});const ProductHome = () => {  const router = useRouter();  const { id } = router.query;  if (!id) return <></>; //防止因为第一次渲染没拿到id而出问题  const product = getProductById(id as string);  return (    <div>        ...          <ProductCard product={product} all />    </div>  );};

另外,我也想到另外可以使用Nextjs提供的SSG功能,让需要动态捞取资料的部份先放到getStaticProps中,让Server端在第一次渲染之前就先获取资料,然后再建置出完整的html档案。

import {GetStaticProps} from 'next';interface Post {  userId: number;  id: number;  title: string;  body: string;}export const getStaticProps:GetStaticProps = async ()=>{  const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");  const post: Post = await res.json();  return {    props: {      post,    },  }}

这个方法我没有实际试过但我认为应该可行!

以下是官方列的问题情境:

情境2: 使用不正当的预先动作

下方是个简单範例告诉你当Client端与Server端分开渲染会发生什么事

function MyComponent() {  // This condition depends on `window`.   // During the first render of the browser the `color` variable will be different  const color = typeof window !== 'undefined' ? 'red' : 'blue'    // As color is passed as a prop there is a mismatch   // between what was rendered server-side vs what was rendered in the first render  return <h1 className={`title ${color}`}>Hello World!</h1>}

上方color在server端会是”blue”,但在Client端 (Browser)会是 “red”!!!

解决方法是使用useState()与useEffect(),依据官方描述,因为useEffect()只会在Client端运作,所以可以确保先后顺序是Client触发了,Client与Server再一起改变!! (而不是两者先各做各的)

// In order to prevent the first render from being different // you can use `**useEffect`** which is only executed in the browser // and is executed during hydrationimport { useEffect, useState } from 'react'function MyComponent() {  // The default value is 'blue',   // it will be used during pre-rendering   // and the first render in the browser (hydration)  const [color, setColor] = useState('blue')    // During hydration `useEffect` is called. `window` is available in `useEffect`.   // In this case because we know we're in the browser checking for window is not needed.  // If you need to read something from window that is fine.  // By calling `setColor` in `useEffect` a render is triggered after hydrating,   // this causes the "browser specific" value to be available. In this case 'red'.  useEffect(() => {setColor('red')}, [])    // As color is a state passed as a prop there is no mismatch   // between what was rendered server-side vs what was rendered in the first render.   // After useEffect runs the color is set to 'red'  return <h1 className={`title ${color}`}>Hello World!</h1>}

上面範例的意思是,因为使用useState,所以Server与Client一开始都会看到color是”Blue”!!!

当前后端渲染完成后,由Client触发useEffect()做componentDidMount()触发setColor(),改变color的同时前后端也会同步,因此两者对于color的认知都会变成color=”red”!!!

情境3: 使用不正当的HTML结构

Invalid HTML may cause hydration mismatch (such as div inside p)

这个意思是说,如果在HTML结构上使用法不正确,可能会导致Client与Server看到的结果不一样。

我的认知是,因为Client是在Browser上运作,受限于Browser规範导致错误的HTML在Client会以不一样的姿态渲染,导致两者不同。

官方範例:

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>  )

官方列出可能发生在css-in-js类型的libraries:

When using Styled Components / EmotionWhen using other css-in-js libraries

参考资料:
https://github.com/vercel/next.js/discussions/35773#discussioncomment-2840696
https://nextjs.org/docs/messages/react-hydration-error


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章