错误: 文本内容与服务器渲染的HTML不匹配

9

我正在尝试在next.js中执行一个简单的算法,但是我遇到了这些水合错误。

这是我正在使用的代码:

import numeros from "../../functions/numberGenerators.js"

export default function teste(){
    let number = numeros()
    return number.map(n =>  
    <div key={n}>
        Number: {n}
    </div>)
}

并且:

export default function megaSena(qtde = 6){
    let listNumbers = []
    while(listNumbers.length <= qtde - 1){
        const numeroRandom = parseInt(Math.random() * 60) + 1
        if (!listNumbers.includes(numeroRandom)){
            listNumbers.push(numeroRandom)
        }
    }
    return listNumbers
}

我有以下错误:
1° - 错误:水合失败,因为初始UI与服务器上呈现的不匹配。
2° - 错误:文本内容与服务器呈现的HTML不匹配。
3° - 错误:水合失败,因为初始UI与服务器上呈现的不匹配。
4° - 错误:水合时出错。 因为错误发生在“悬停”边界之外,整个根将切换到客户端呈现。
我该怎么做才能解决这个问题?

不匹配的原因是页面在服务器端预渲染时生成的随机数与在客户端进行hydration期间生成的随机数不匹配。您应该将随机数生成逻辑仅移动到客户端(在useEffect内部)。 - juliomalves
3个回答

21
这些错误信息本质上都是在说同一件事情:服务器返回的HTML与你的应用程序渲染的不匹配。

为什么会出现这种情况?

当调用teste时,它并不总是返回相同的内容。它被设计成显示随机数字。

这意味着它将每次渲染都显示不同的数字列表

使用像NextJS这样的框架进行服务端渲染(SSR)时,它使用React hydration。这只是一个花哨的词,意思是React正在找出如何将从HTML文件渲染的DOM结构转换为运行中的单页应用程序(SPA)。

为了使其正常工作,React Hydration要求来自服务器的HTML和客户端应用程序渲染的内容完全匹配

因此,在您的场景中,发生了以下情况:

  1. 服务器需要生成HTML文件。
  2. 它调用teste,生成一个随机数组并呈现它。
  3. 服务器将此HTML发送给客户端。
  4. 客户端应用程序调用teste,生成一个新的随机数组并呈现它。
  5. 它试图“水化”应用程序,但HTML和客户端渲染不匹配。
  6. NextJS/React不知道如何处理这种不匹配,因此记录了您看到的错误。

如何解决这个问题?

修复很简单,但需要您注意何时需要进行修复。

常见的方法是利用useEffect()确保服务器和客户端在水化过程中呈现相同的内容,并仅在之后在客户端呈现动态内容。

最简单的方法是什么都不渲染。使用您的代码,它看起来像这样:

import React from "react";

export default function Teste() {
    const [hydrated, setHydrated] = React.useState(false);
    React.useEffect(() => {
        setHydrated(true);
    }, []);
    if (!hydrated) {
        // Returns null on first render, so the client and server match
        return null;
    }

    let number = numeros();
    return number.map((n) => <div key={n}>Number: {n}</div>);
}

这段代码确保第一次呈现 Teste 将返回 null
这个首次呈现是服务器用来生成HTML文件的,也是客户端应用程序在"hydration"(注水)过程中使用的。
在此第一次运行期间,hydrated 将具有默认值false,这将导致组件返回 null。同时,在此第一次运行期间,useEffect() 将调用setHydrated(true),这会在第一个完成后触发第二次呈现。
当第二次呈现运行时,应用程序已经被hydrate了,因此不需要再担心错误发生了。此时,hydrated将是true,因此随机数将正常呈现。
更多信息
如果您想了解有关React hydration的更多信息,我写了一篇博文,介绍如何修复这些类型的错误
我还发布了一个NPM包,帮助简化处理这些类型的水合错误:react-hydration-provider 使用react-hydration-provider来修复您的错误,您的代码将如下所示:
import { HydrationProvider, Client } from "react-hydration-provider";

function App() {
    return (
        <HydrationProvider>
            <Client>
                <Teste />
            </Client>
        </HydrationProvider>
    );
}

function Teste() {
    let number = numeros();
    return number.map((n) => <div key={n}>Number: {n}</div>);
}

这将使得Teste只有在应用程序完成注水后才会在客户端渲染。
你也可以做一些更复杂的事情,例如:
import { HydrationProvider, Server, Client } from "react-hydration-provider";

function App() {
    return (
        <HydrationProvider>
            <Teste />
        </HydrationProvider>
    );
}

function Teste() {
    let number = numeros();
    return number.map((n) => (
        <div key={n}>
            <span>Number: </span>
            <Client>{n}</Client>
            <Server>Loading...</Server>
        </div>
    ));
}

这将使您的应用程序在开始时为每个数字呈现加载消息,然后在应用程序完成水合过程后用随机数字替换它。

8

就像 @milad-amirvafa 所说的那样,

注释掉代码的一部分,以获取导致水合错误的元素,然后添加一个状态钩子,初始状态为 false,例如:const [hydrated, setHydrated] = useState(false);

然后在 useEffect 钩子中将该状态设置为 true,将 hydrated 状态添加到导致错误的组件中。您的代码应如下所示:

const Component = () => {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
    setHydrated(true);
},[])
return (
    <>
        {hydrated && <div>The element which throws error</div>}
    </>
)} 
export default Component;

太感谢了!这解决了错误。顺便问一下,这个解决方法有什么副作用吗? - Fusion Developers
目前我不知道任何的。 - mjosh
它消除了本地错误。谢谢。 - davychhouk
1
@FusionDevelopers 是的,这个方法有副作用。它只是从你的服务器渲染的 HTML 中删除元素,这可能会影响 SEO。它还会对用户体验产生影响,因为在那个元素不被渲染的短暂时刻内,用户可能会感到不适。如果您感兴趣,我上面的回答有更多关于这个方法如何工作的细节。 - twiz

0
请按照以下解决方案解决问题。 这会使组件在第一次渲染时不被呈现。
const Component = () => {
    const [showComponent, setShowComponent] = useState(false);
    useEffect(() => {
        setShowComponent(true);
    },[])
    return (
        <>
            showComponent && <div>The component which throws error</div>
        </>
    )
}

export default Component

目前你的回答不够清晰。请编辑并添加更多细节,以帮助其他人理解它如何回答所提出的问题。你可以在帮助中心找到有关如何撰写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接