我尝试在 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° - 错误:补水时出现错误。因为错误发生在 Suspense 边界之外,所以整个根将切换到客户端渲染。
我可以做什么来解决这个问题?
这些错误消息本质上都说了同样的事情:
服务器中的 HTML 与您的应用程序呈现的内容不匹配。
当调用
teste
时,它并不总是返回相同的东西。它旨在显示随机数。
这意味着它将显示不同的数字列表,每次
teste
被渲染。
当将服务器端渲染(SSR)与 NextJS 等框架一起使用时,它正在使用 React Hydration。这只是一个花哨的词,表示 React 正在研究如何获取从 HTML 文件渲染的 DOM 结构并将其转换为正在运行的单页应用程序 (SPA)。
为了使其正常工作,React Hydration 要求服务器中的 HTML 与客户端应用程序呈现的内容完全匹配。
所以在你的场景中,这就是正在发生的事情:
teste
,生成随机数字数组并渲染它。
teste
,它生成一个NEW 随机数字数组并呈现它。
常见的方法是利用
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 文件的内容,也是客户端应用程序将用于“水化”过程的内容。
在第一次运行期间,
hydrated
将具有默认值
false
,这将导致组件返回
null
。同样,在第一次运行中,
useEffect()
将调用
setHydrated(true)
,这将在第一次渲染完成后触发第二次渲染。当第二次渲染运行时,应用程序已经水合了,因此无需再担心发生错误。此时
hydrated
将变为
true
,因此随机数将正常渲染。更多信息
关于修复这些类型的错误的博客文章。
我还发布了一个 NPM 包,有助于简化处理这些类型的水合错误:要使用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>
));
}
这将允许您的应用程序最初为每个数字呈现一条加载消息,然后在应用程序完成水合过程后将其替换为随机数字。
注释掉代码的某些部分以获取导致水合错误的元素,然后添加一个以 false 作为初始状态的状态钩子,如下所示,
const [hydrated, setHydrated] = useState(false)
;然后在 useEffect 钩子中将该状态设置为 true,将水合状态添加到导致错误的组件中。您的代码应如下所示:
const Component = () => {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
setHydrated(true);
},[])
return (
<>
{hydrated && <div>The element which throws error</div>}
</>
)}
export default Component;
const Component = () => {
const [showComponent, setShowComponent] = useState(false);
useEffect(() => {
setShowComponent(true);
},[])
return (
<>
showComponent && <div>The component which throws error</div>
</>
)
}
export default Component
next.js official documentation
。nextjs.org. 中提供了解决许多此类问题的简单方法 这个问题的解决方案简单解决方案在这里: