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
和包含componentStack
的errorInfo
物件呼叫。 - **選用**
onUncaughtError
:當擲出錯誤且未被錯誤邊界攔截時呼叫的回呼函式。使用擲出的error
和包含componentStack
的errorInfo
物件呼叫。 - (選填)
onRecoverableError
:當 React 自動從錯誤中恢復時呼叫的回呼函式。它會接收 React 拋出的error
以及一個包含componentStack
的errorInfo
物件。某些可恢復的錯誤可能包含原始錯誤原因,作為error.cause
。 - (選填)
identifierPrefix
:React 用於由useId
產生的 ID 的字串前綴。在同一個頁面上使用多個根元素時,這有助於避免衝突。必須與伺服器上使用的前綴相同。
- **選用**
回傳值
hydrateRoot
會回傳一個物件,其中包含兩個方法:render
和 unmount
。
注意事項
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 元素、字串、數字、null
或undefined
。
回傳值
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,你的元件將會使用狀態。
水合整個文件
完全使用 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 的根組件
在根完成 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 選項是一個以兩個參數呼叫的函式
- 拋出的 錯誤。
- 一個包含錯誤 componentStack 的 errorInfo 物件。
您可以使用 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 選項是一個以兩個參數呼叫的函式
- 邊界攔截的 錯誤。
- 一個包含錯誤 componentStack 的 errorInfo 物件。
您可以使用 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 選項是一個函式,它會接收兩個參數:
- React 拋出的 error(錯誤)。有些錯誤可能包含原始原因,例如 error.cause。
- 一個包含錯誤 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(...)
。
要修復此問題,請將根選項傳遞給 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});