hydrateRoot 能讓你在瀏覽器 DOM 節點中顯示 React 元件,而該節點的 HTML 內容先前是由 react-dom/server 產生的。

const root = hydrateRoot(domNode, reactNode, options?)

參考

hydrateRoot(domNode, reactNode, options?)

呼叫 hydrateRoot 將 React「附加」到伺服器環境中已由 React 渲染的現有 HTML。

import { hydrateRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, reactNode);

React 將附加到 domNode 內的 HTML,並接管其中 DOM 的管理。完全使用 React 建構的應用程式通常只會對其根元件進行一次 hydrateRoot 呼叫。

請參閱以下更多範例。

參數

  • domNode:在伺服器上渲染為根元素的 DOM 元素

  • reactNode:用於渲染現有 HTML 的「React 節點」。這通常是一段 JSX 程式碼,例如 <App />,它是使用 ReactDOM 伺服器 方法(例如 renderToPipeableStream(<App />))渲染的。

  • **選用** options:包含此 React 根選項的物件。

    • **選用** onCaughtError:當 React 在錯誤邊界中攔截到錯誤時呼叫的回呼函式。使用錯誤邊界攔截到的 error 和包含 componentStackerrorInfo 物件呼叫。
    • **選用** onUncaughtError:當擲出錯誤且未被錯誤邊界攔截時呼叫的回呼函式。使用擲出的 error 和包含 componentStackerrorInfo 物件呼叫。
    • (選填) onRecoverableError:當 React 自動從錯誤中恢復時呼叫的回呼函式。它會接收 React 拋出的 error 以及一個包含 componentStackerrorInfo 物件。某些可恢復的錯誤可能包含原始錯誤原因,作為 error.cause
    • (選填) identifierPrefix:React 用於由 useId 產生的 ID 的字串前綴。在同一個頁面上使用多個根元素時,這有助於避免衝突。必須與伺服器上使用的前綴相同。

回傳值

hydrateRoot 會回傳一個物件,其中包含兩個方法:renderunmount

注意事項

  • hydrateRoot() 預期渲染的內容與伺服器端渲染的內容相同。您應該將不符視為錯誤並修復它們。
  • 在開發模式中,React 會在 hydration 過程中針對不符發出警告。不保證在不符的情況下,屬性差異會被修補。這對於效能至關重要,因為在大多數應用程式中,不符的情況很少見,因此驗證所有標記的成本過高。
  • 您的應用程式中可能只會有一個 hydrateRoot 呼叫。如果您使用框架,它可能會為您執行此呼叫。
  • 如果您的應用程式是客戶端渲染的,沒有預先渲染的 HTML,則不支援使用 hydrateRoot()。請改用 createRoot()

root.render(reactNode)

呼叫 root.render 來更新瀏覽器 DOM 元素中已 hydration 的 React 根元素內的 React 元件。

root.render(<App />);

React 將會更新已 hydration 的 root 中的 <App />

請參閱以下更多範例。

參數

  • reactNode:您想要更新的「React 節點」。這通常是一段 JSX,例如 <App />,但您也可以傳遞使用 createElement() 建構的 React 元素、字串、數字、nullundefined

回傳值

root.render 回傳 undefined

注意事項

  • 如果您在根元素完成 hydration 之前呼叫 root.render,React 將會清除現有的伺服器端渲染的 HTML 內容,並將整個根元素切換到客戶端渲染。

root.unmount()

呼叫 root.unmount 來銷毀 React 根元素內已渲染的樹狀結構。

root.unmount();

完全使用 React 建構的應用程式通常不會呼叫 root.unmount

如果你的 React 根節點的 DOM 節點(或其任何祖先節點)可能被其他程式碼從 DOM 中移除,這個方法就很有用。例如,想像一個 jQuery 標籤面板,它會從 DOM 中移除非活動的標籤頁。如果一個標籤頁被移除,它裡面的所有內容(包括裡面的 React 根節點)也會從 DOM 中移除。你需要透過呼叫 root.unmount 來告訴 React「停止」管理已移除根節點的內容。否則,已移除根節點內的元件將不會清理並釋放資源,例如訂閱。

呼叫 root.unmount 將會卸載根節點中的所有元件,並將 React 從根 DOM 節點「分離」,包括移除樹狀結構中的任何事件處理程式或狀態。

參數

root.unmount 不接受任何參數。

回傳值

root.unmount 回傳 undefined

注意事項

  • 呼叫 root.unmount 將卸載樹狀結構中的所有元件,並將 React 從根 DOM 節點「分離」。

  • 一旦你呼叫了 root.unmount,你就不能在該根節點上再次呼叫 root.render。嘗試在已卸載的根節點上呼叫 root.render 將會拋出「無法更新已卸載的根節點」錯誤。


用法

替伺服器渲染的 HTML 進行水合 (Hydrating)

如果你的應用程式的 HTML 是由 react-dom/server 產生的,你需要在客戶端對其進行*水合 (hydrate)*。

import { hydrateRoot } from 'react-dom/client';

hydrateRoot(document.getElementById('root'), <App />);

這會將 瀏覽器 DOM 節點 內的伺服器 HTML 與應用程式的 React 元件 進行水合。通常,你會在啟動時執行一次。如果你使用框架,它可能會在幕後為你執行此操作。

為了替你的應用程式進行水合,React 會將你的元件邏輯「附加」到伺服器產生的初始 HTML。水合會將伺服器的初始 HTML 快照轉換為在瀏覽器中執行的完整互動式應用程式。

import './styles.css';
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(
  document.getElementById('root'),
  <App />
);

你不需要再次呼叫 hydrateRoot 或在更多地方呼叫它。從現在開始,React 將會管理你的應用程式的 DOM。要更新 UI,你的元件將會使用狀態

陷阱

你傳遞給 hydrateRoot 的 React 樹狀結構需要產生與伺服器端**相同的輸出**。

這對使用者體驗來說非常重要。在你的 JavaScript 程式碼載入之前,使用者會花一些時間觀看伺服器產生的 HTML。伺服器渲染透過顯示其輸出的 HTML 快照來創造應用程式載入更快的錯覺。突然顯示不同的內容會破壞這種錯覺。這就是為什麼伺服器渲染輸出必須與客戶端上的初始渲染輸出相匹配的原因。

導致水合錯誤的最常見原因包括:

  • 根節點內 React 產生的 HTML 周圍有多餘的空格(例如換行符)。
  • 在你的渲染邏輯中使用像 typeof window !== 'undefined' 這樣的檢查。
  • 在你的渲染邏輯中使用僅限瀏覽器的 API,例如 window.matchMedia
  • 在伺服器和客戶端上渲染不同的資料。

React 可以從某些水合錯誤中恢復,但**你必須像修復其他錯誤一樣修復它們。** 在最好的情況下,它們會導致速度變慢;在最壞的情況下,事件處理程式可能會附加到錯誤的元素上。


水合整個文件

完全使用 React 建構的應用程式可以將整個文件渲染為 JSX,包括 <html> 標籤。

function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}

若要讓整個文件套用 hydration,請將全域 document 作為第一個參數傳遞給 hydrateRoot

import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(document, <App />);

抑制不可避免的 hydration 不匹配錯誤

如果單個元素的屬性或文字內容在伺服器和客戶端之間不可避免地不同(例如,時間戳),您可以隱藏 hydration 不匹配警告。

若要隱藏元素上的 hydration 警告,請添加 suppressHydrationWarning={true}

export default function App() {
  return (
    <h1 suppressHydrationWarning={true}>
      Current Date: {new Date().toLocaleDateString()}
    </h1>
  );
}

這僅作用於一層深度,旨在作為一個例外處理方式。 不要過度使用它。 除非它是文字內容,否則 React 仍然不會嘗試修補它,因此它可能會在未來的更新之前保持不一致。


處理不同的客戶端和伺服器內容

如果您有意需要在伺服器和客戶端上渲染不同的內容,您可以執行兩次渲染。 在客戶端上渲染不同內容的組件可以讀取像 isClient 這樣的 狀態變數,您可以在 Effect 中將其設置為 true

import { useState, useEffect } from "react";

export default function App() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  return (
    <h1>
      {isClient ? 'Is Client' : 'Is Server'}
    </h1>
  );
}

這樣,初始渲染過程將渲染與伺服器相同的內容,避免不匹配,但在 hydration 之後會立即同步進行額外的渲染過程。

陷阱

這種方法會降低 hydration 速度,因為您的組件必須渲染兩次。 請注意低速連線上的使用者體驗。 JavaScript 程式碼的載入時間可能比初始 HTML 渲染晚得多,因此在 hydration 後立即渲染不同的 UI 也可能會讓使用者感到不順暢。


更新已套用 hydration 的根組件

在根完成 hydration 後,您可以呼叫 root.render 來更新根 React 組件。 createRoot 不同,您通常不需要這樣做,因為初始內容已經以 HTML 呈現。

如果您在 hydration 後的某個時間點呼叫 root.render,並且組件樹結構與先前渲染的內容相符,React 將會 保留狀態。 請注意您可以如何在輸入框中輸入,這表示在此範例中,每秒重複呼叫 render 的更新並非破壞性的

import { hydrateRoot } from 'react-dom/client';
import './styles.css';
import App from './App.js';

const root = hydrateRoot(
  document.getElementById('root'),
  <App counter={0} />
);

let i = 0;
setInterval(() => {
  root.render(<App counter={i} />);
  i++;
}, 1000);

在已套用 hydration 的根上呼叫 root.render 並不常見。 通常,您會在其中一個組件內 更新狀態

顯示未攔截錯誤的對話框

預設情況下,React 會將所有未攔截的錯誤記錄到主控台。 若要實作您自己的錯誤報告,您可以提供選用的 onUncaughtError 根選項

import { hydrateRoot } from 'react-dom/client';

const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onUncaughtError: (error, errorInfo) => {
console.error(
'Uncaught error',
error,
errorInfo.componentStack
);
}
}
);
root.render(<App />);

onUncaughtError 選項是一個以兩個參數呼叫的函式

  1. 拋出的 錯誤
  2. 一個包含錯誤 componentStackerrorInfo 物件。

您可以使用 onUncaughtError 根選項來顯示錯誤對話框

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {reportUncaughtError} from "./reportError";
import "./styles.css";
import {renderToString} from 'react-dom/server';

const container = document.getElementById("root");
const root = hydrateRoot(container, <App />, {
  onUncaughtError: (error, errorInfo) => {
    if (error.message !== 'Known error') {
      reportUncaughtError({
        error,
        componentStack: errorInfo.componentStack
      });
    }
  }
});

顯示錯誤邊界錯誤

預設情況下,React 會將錯誤邊界攔截的所有錯誤記錄到 console.error。 若要覆寫此行為,您可以為 錯誤邊界 攔截的錯誤提供選用的 onCaughtError 根選項

import { hydrateRoot } from 'react-dom/client';

const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onCaughtError: (error, errorInfo) => {
console.error(
'Caught error',
error,
errorInfo.componentStack
);
}
}
);
root.render(<App />);

onCaughtError 選項是一個以兩個參數呼叫的函式

  1. 邊界攔截的 錯誤
  2. 一個包含錯誤 componentStackerrorInfo 物件。

您可以使用 onCaughtError 根選項來顯示錯誤對話框或從記錄中篩選已知錯誤

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {reportCaughtError} from "./reportError";
import "./styles.css";

const container = document.getElementById("root");
const root = hydrateRoot(container, <App />, {
  onCaughtError: (error, errorInfo) => {
    if (error.message !== 'Known error') {
      reportCaughtError({
        error,
        componentStack: errorInfo.componentStack
      });
    }
  }
});

顯示可復原 hydration 不匹配錯誤的對話框

當 React 遇到 hydration 不匹配時,它會自動嘗試透過在客戶端渲染來恢復。預設情況下,React 會將 hydration 不匹配錯誤記錄到 console.error。若要覆蓋此行為,您可以提供選用的根選項 onRecoverableError

import { hydrateRoot } from 'react-dom/client';

const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onRecoverableError: (error, errorInfo) => {
console.error(
'Caught error',
error,
error.cause,
errorInfo.componentStack
);
}
}
);

onRecoverableError 選項是一個函式,它會接收兩個參數:

  1. React 拋出的 error(錯誤)。有些錯誤可能包含原始原因,例如 error.cause
  2. 一個包含錯誤 componentStack(組件堆疊)的 errorInfo(錯誤資訊)物件。

您可以使用根選項 onRecoverableError 來顯示 hydration 不匹配的錯誤對話框。

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {reportRecoverableError} from "./reportError";
import "./styles.css";

const container = document.getElementById("root");
const root = hydrateRoot(container, <App />, {
  onRecoverableError: (error, errorInfo) => {
    reportRecoverableError({
      error,
      cause: error.cause,
      componentStack: errorInfo.componentStack
    });
  }
});

疑難排解

我收到一個錯誤:「您傳遞了第二個參數給 root.render」...(svg code same as above)...

一個常見的錯誤是將 hydrateRoot 的選項傳遞給 root.render(...)

主控台
警告:您傳遞了第二個參數給 root.render(…),但它只接受一個參數。

要修復此問題,請將根選項傳遞給 hydrateRoot(...),而不是 root.render(...)

// 🚩 Wrong: root.render only takes one argument.
root.render(App, {onUncaughtError});

// ✅ Correct: pass options to createRoot.
const root = hydrateRoot(container, <App />, {onUncaughtError});
...(link code same as above)...