什么是水合

所谓水合(Hydration),指的是 React 为预渲染的 HTML 添加事件处理程序,将其转为完全可交互的应用程序的过程。

React 提供了 hydrateRoot 客户端 API,通常搭配 react-dom/server 一起使用。先由 react-dom/server 生成 HTML,再调用 hydrateRoot 为生成的 HTML 进行水合,伪代码如下:

# 服务端
import { renderToString } from 'react-dom/server';
const html = renderToString(<App />);

# 客户端
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);

与我们使用 React 时常用的 createRoot 不同,creatRoot 会重新创建 DOM 节点,而 hydrateRoot 会尽可能复用已有的 DOM 节点。

那具体是怎么进行水合的呢?

这就要说到 React 的渲染原理了。你可以这样简单粗暴的理解:

当调用 hydateRoot 的时候,会传入组件(例子中的 ),React 会据此构建 React 组件树,并按照 组件树的顺序遍历真实的 DOM 树,判断 DOM 树和组件树是否对应,如何对应,则跳过创建 DOM 节点的环节,复用当前 DOM 节点,添加事件并进行关联。

所以水合的前提是 DOM 树和组件树渲染一致。

常见的水合错误

  1. HTML 元素错误嵌套

export default function App() {
  return (
    <p>
      text1
      <p>text2</p>
    </p>
  );
}
  1. 渲染时使用 typeof window !== 'undefined' 等判断

"use client";

export default function App() {
  const isClient = typeof window !== "undefined";
  return <h1>{isClient ? "Client" : "Server"}</h1>;
}
  1. 渲染时使用客户端 API 如 window、localStorage 等

"use client";

export default function App() {
  return (
    <h1>
      {typeof localStorage !== "undefined" ? localStorage.getItem("name") : ""}
    </h1>
  );
}
  1. 使用时间相关的 API,如 Date.now()

"use client";

export default function App() {
  return <h1>{Date.now()}</h1>;
}

解决方法

  1. 是使用 useEffect 来处理:

import { useEffect, useState } from "react";

export default function App() {
  const [time, setTime] = useState(Date.now());
  useEffect(() => {
    const timer = setInterval(() => {
      setTime(Date.now());
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <h1>{time}</h1>;
}
  1. 禁用特定组件的 SSR 渲染

为什么会渲染不一致呢?本质上还是客户端组件既在服务端也在客户端渲染一份,干脆取消掉客户端组件的服务端渲染,为此需要借助 Next.js 提供的 dynamic 函数。

import dynamic from "next/dynamic";

export default function App() {
  const DynamicComponent = dynamic(() => import("./DynamicComponent"), {
    ssr: false,
  });
  return <DynamicComponent />;
}
  1. 使用 suppressHydrationWarning 取消错误提示

export default function App() {
  return (
    <div suppressHydrationWarning>
      {typeof window !== "undefined" ? "Client" : "Server"}
    </div>
  );
}
  1. 自定义 hook

最后更新于

这有帮助吗?