hydrateRootは、以前にreact-dom/serverによって生成されたHTMLコンテンツを持つブラウザDOMノード内にReactコンポーネントを表示できるようにします。

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

リファレンス

hydrateRoot(domNode, reactNode, options?)

hydrateRootを呼び出して、サーバー環境でReactによって既にレンダリングされた既存のHTMLにReactを「アタッチ」します。

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

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

Reactは、domNode内にあるHTMLにアタッチし、その中のDOMの管理を引き継ぎます。Reactで完全に構築されたアプリは、通常、ルートコンポーネントで1つのhydrateRoot呼び出しのみを持ちます。

以下の例を参照してください。

パラメータ

  • domNode: サーバーでルート要素としてレンダリングされたDOM要素

  • reactNode: 既存のHTMLをレンダリングするために使用される「Reactノード」。これは通常、<App />のようなJSXの一部であり、renderToPipeableStream(<App />)のようなReactDOM Serverメソッドでレンダリングされました。

  • オプション options: このReactルートのオプションを持つオブジェクト。

    • オプション onCaughtError: Reactがエラー境界でエラーをキャッチしたときに呼び出されるコールバック。エラー境界でキャッチされたerrorと、componentStackを含むerrorInfoオブジェクトとともに呼び出されます。
    • オプション onUncaughtError: エラーがスローされ、エラー境界で捕捉されなかった場合に呼び出されるコールバック。スローされたerrorと、componentStackを含むerrorInfoオブジェクトとともに呼び出されます。
    • オプション onRecoverableError: Reactが自動的にエラーから回復したときに呼び出されるコールバック。Reactがスローするerrorと、componentStackを含むerrorInfoオブジェクトとともに呼び出されます。一部の回復可能なエラーには、元のエラーの原因がerror.causeとして含まれる場合があります。
    • オプション identifierPrefix: ReactがuseIdによって生成されるIDに使用する文字列プレフィックス。同じページで複数のルートを使用する場合の競合を避けるのに役立ちます。サーバーで使用したものと同じプレフィックスである必要があります。

以下の値を返します

hydrateRootは、2つのメソッドを持つオブジェクトを返します。renderunmountです。

注意事項

  • hydrateRoot()は、レンダリングされたコンテンツがサーバーでレンダリングされたコンテンツと同一であることを期待します。不一致はバグとして扱い、修正する必要があります。
  • 開発モードでは、Reactはハイドレーション中の不一致について警告します。不一致が発生した場合、属性の違いがパッチされる保証はありません。ほとんどのアプリでは不一致がまれであり、すべてのマークアップを検証すると非常にコストがかかるため、これはパフォーマンス上の理由で重要です。
  • アプリ内ではhydrateRootの呼び出しは1回のみになるでしょう。フレームワークを使用している場合は、この呼び出しをフレームワークが行う場合があります。
  • アプリがクライアント側でレンダリングされ、HTMLがまだレンダリングされていない場合は、hydrateRoot()の使用はサポートされていません。代わりにcreateRoot()を使用してください。

root.render(reactNode)

root.renderを呼び出して、ブラウザのDOM要素のハイドレートされたReactルート内のReactコンポーネントを更新します。

root.render(<App />);

Reactは、ハイドレートされたroot内の<App />を更新します。

以下の例を参照してください。

パラメータ

  • reactNode: 更新したい "Reactノード"。これは通常、<App />のようなJSXですが、createElement()で構築されたReact要素、文字列、数値、null、またはundefinedを渡すこともできます。

以下の値を返します

root.renderは、undefinedを返します。

注意事項

  • ルートのハイドレーションが完了する前にroot.renderを呼び出すと、Reactは既存のサーバーでレンダリングされたHTMLコンテンツをクリアし、ルート全体をクライアントレンダリングに切り替えます。

root.unmount()

Reactルート内のレンダリングされたツリーを破棄するには、root.unmountを呼び出します。

root.unmount();

Reactで完全に構築されたアプリでは、通常、root.unmountを呼び出すことはありません。

これは、ReactルートのDOMノード(またはその祖先のいずれか)が他のコードによってDOMから削除される可能性がある場合に主に役立ちます。たとえば、非アクティブなタブをDOMから削除するjQueryのタブパネルを想像してください。タブが削除されると、その内部にあるすべてのもの(内部のReactルートを含む)もDOMから削除されます。 root.unmountを呼び出して、Reactに削除されたルートのコンテンツの管理を「停止」するように指示する必要があります。そうしないと、削除されたルート内のコンポーネントは、サブスクリプションなどのリソースをクリーンアップして解放しません。

root.unmountを呼び出すと、ルート内のすべてのコンポーネントがアンマウントされ、ツリー内のイベントハンドラーや状態の削除を含め、ReactがルートDOMノードから「切り離され」ます。

パラメータ

root.unmountはパラメータを受け入れません。

戻り値

root.unmountundefinedを返します。

注意点

  • root.unmountを呼び出すと、ツリー内のすべてのコンポーネントがアンマウントされ、ReactがルートDOMノードから「切り離され」ます。

  • root.unmountを呼び出すと、そのルートで再びroot.renderを呼び出すことはできません。アンマウントされたルートでroot.renderを呼び出そうとすると、「アンマウントされたルートを更新できません」というエラーがスローされます。


使用法

サーバーでレンダリングされたHTMLのハイドレーション

アプリのHTMLがreact-dom/serverによって生成された場合、クライアント側でハイドレーションする必要があります。

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'のようなチェックを使用する。
  • レンダリングロジックで、window.matchMedia のようなブラウザ専用APIを使用すること。
  • サーバーとクライアントで異なるデータをレンダリングすること。

Reactはいくつかのハイドレーションエラーから回復しますが、他のバグと同様に修正する必要があります。 最良の場合、それらは速度低下につながり、最悪の場合、イベントハンドラが間違った要素にアタッチされる可能性があります。


ドキュメント全体のハイドレーション

Reactで完全に構築されたアプリは、<html> タグを含む、ドキュメント全体をJSXとしてレンダリングできます。

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>
);
}

ドキュメント全体をハイドレートするには、hydrateRoot の最初の引数として、document グローバルを渡します。

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

hydrateRoot(document, <App />);

回避不可能なハイドレーションの不一致エラーの抑制

単一の要素の属性またはテキストコンテンツが、サーバーとクライアント間で必然的に異なる場合(たとえば、タイムスタンプ)、ハイドレーションの不一致警告を抑制できます。

要素に関するハイドレーション警告を抑制するには、suppressHydrationWarning={true} を追加します。

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

これは1レベルの深さでのみ機能し、エスケープハッチとして意図されています。乱用しないでください。テキストコンテンツでない限り、Reactはそれを修正しようとしないため、今後の更新まで一貫性がなくなる可能性があります。


異なるクライアントとサーバーのコンテンツの処理

サーバーとクライアントで意図的に異なるものをレンダリングする必要がある場合は、2パスレンダリングを実行できます。クライアントで異なるものをレンダリングするコンポーネントは、state変数であるisClient のようなものを読み取ることができます。これは、Effecttrue に設定できます。

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>
  );
}

このようにすると、初期レンダリングパスはサーバーと同じコンテンツをレンダリングし、不一致を回避しますが、ハイドレーションの直後に同期的に追加のパスが発生します。

落とし穴

このアプローチでは、コンポーネントが2回レンダリングする必要があるため、ハイドレーションが遅くなります。低速な接続でのユーザーエクスペリエンスに注意してください。JavaScriptコードは初期HTMLレンダリングよりも大幅に遅れて読み込まれる可能性があるため、ハイドレーションの直後に異なるUIをレンダリングすると、ユーザーに不快感を与える可能性があります。


ハイドレートされたルートコンポーネントの更新

ルートのハイドレーションが完了したら、root.render を呼び出して、ルートのReactコンポーネントを更新できます。createRoot の場合とは異なり、通常はこれを行う必要はありません。これは初期コンテンツがすでにHTMLとしてレンダリングされているためです。

ハイドレーション後にどこかの時点で 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);

ハイドレートされたルートで root.render を呼び出すことはまれです。通常は、代わりにコンポーネントの1つの中で状態を更新します。

キャッチされないエラーのダイアログを表示する

デフォルトでは、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 オプションは、2つの引数で呼び出される関数です。

  1. スローされた エラー
  2. errorInfo オブジェクト。エラーの componentStack が含まれています。

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オプションは、2つの引数で呼び出される関数です。

  1. 境界によって捕捉されたエラーです。
  2. errorInfo オブジェクト。エラーの componentStack が含まれています。

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
      });
    }
  }
});

回復可能なハイドレーションミスマッチエラーのダイアログを表示する

Reactがハイドレーションミスマッチを検出すると、クライアント上でレンダリングすることで自動的に回復を試みます。デフォルトでは、Reactはハイドレーションミスマッチエラーを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オプションは、2つの引数で呼び出される関数です。

  1. Reactがスローするエラー。一部のエラーには、元の原因がerror.causeとして含まれている場合があります。
  2. エラーのcomponentStackを含むerrorInfoオブジェクト。

onRecoverableErrorルートオプションを使用して、ハイドレーションミスマッチのエラーダイアログを表示できます。

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に2番目の引数を渡しました」というエラーが発生する

よくある間違いは、hydrateRootのオプションをroot.render(...)に渡してしまうことです。

コンソール
警告:root.render(…)に2番目の引数を渡しましたが、引数は1つしか受け付けません。

修正するには、ルートオプションをroot.render(...)ではなく、hydrateRoot(...)に渡してください。

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

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