browser
browser を使うことで、サーバレンダリング中にコンポーネントをブラウザ専用としてマークできます。
use(browser(reason?))リファレンス
browser(reason?)
use の中で browser を呼び出して、サーバレンダリング中にコンポーネントをブラウザ専用としてマークします。
import { use } from 'react';
import { browser } from 'react-dom';
function BrowserOnly() {
use(browser('This component requires browser APIs.'));
return <BrowserContent />;
}サーバレンダリング中の場合、use(browser()) はコンポーネントのレンダーを中断し、代わりに最も近い <Suspense> バウンダリによるフォールバックが表示されるようにします。ブラウザでは、use(browser()) は undefined を返し、コンポーネントは通常通りレンダーされます。
引数
- 省略可能
reason: コンテンツをブラウザでレンダーする必要がある理由を説明する文字列または関数。この文字列、または関数の返り値が、onBrowserBailoutに渡されるErrorのcauseになります。サーバレンダラがbrowserの返り値を処理するたびに、React は理由を生成する関数を呼び出します。ブラウザでは呼び出されません。理由の生成にコストがかかる場合に() => new Error(...)のような関数を渡すようにしてください。
返り値
browser は、内部構造が非公開の値を返します。この値は、コンポーネント内で use に渡すことや、サーバでのレンダーを中止する際の理由として使うことが可能です。ブラウザでは、この値を use に渡すと undefined が返されます。
注意点
- サーバレンダリング中は、
use(browser())が<Suspense>バウンダリ内にある必要があります。バウンダリがない場合、サーバでのレンダーは失敗します。 use(browser())は、クライアントコンポーネントから呼び出す必要があります。サーバコンポーネントからは呼び出せません。browser()を単独で呼び出しても何も起きません。コンポーネントをブラウザ専用としてマークするには、browserの返した値をuseに渡してください。この値をスローしないでください。
使用法
ブラウザでのみコンテンツをレンダーする
ブラウザでのみレンダーされるべきコンポーネント内で、use の中で browser を呼び出します。
これは、typeof window を確認する、マウント済み state がエフェクトで設定されるまで待機する、フレームワークのオプションでサーバレンダリングを無効にする、といったテクニックの代わりに使用できるものです。
以下の例で Reload をクリックすると、初期 HTML 内のローディングフォールバックを確認できます。ハイドレーション後、React は localStorage から読み込んだ下書きを表示します。
import { Suspense, use, useState } from 'react'; import { browser } from 'react-dom'; function SavedDraft() { use(browser('The draft is stored in localStorage.')); const [draft, setDraft] = useState( () => localStorage.getItem('draft') ?? '' ); function handleChange(event) { const nextDraft = event.target.value; setDraft(nextDraft); localStorage.setItem('draft', nextDraft); } return ( <label> Draft: <textarea value={draft} onChange={handleChange} rows={4} cols={30} /> </label> ); } export default function App() { return ( <> <h1>Saved draft</h1> <Suspense fallback={<p>Loading draft...</p>}> <SavedDraft /> </Suspense> </> ); }
サーバでのレンダーを条件付きで行う
他の use の呼び出しと同様に、use(browser()) は条件文の中や早期リターンの後でも呼び出せます。これにより、props の値などの条件に基づいて、コンポーネントやカスタムフックをサーバレンダリングの対象から除外できます。
例えば、以下の useTimeZone フックは、省略可能なデフォルト値を受け取ります。React はデフォルト値が渡されるとその値を初期 HTML とブラウザで表示します。デフォルト値がない場合、コンポーネントはサーバレンダリング中にサスペンドし、ブラウザではデバイスのローカルタイムゾーンを表示します。
Reload をクリックすると、ユーザのタイムゾーンが表示される前のローディングフォールバックを確認できます。
import { use } from 'react'; import { browser } from 'react-dom'; export function useTimeZone(defaultTimeZone) { if (defaultTimeZone !== undefined) { return defaultTimeZone; } use(browser('No default time zone was provided.')); return Intl.DateTimeFormat().resolvedOptions().timeZone; }
サスペンス対応のデータ取得ライブラリを使う場合も、同様のパターンで条件に応じてサーバレンダリングを回避できます。
function useBrowserQuery(query, options) {
if (options.initialData === undefined) {
use(browser('useBrowserQuery: No initial data was provided.'));
}
return useQuery(query, options);
}
function ProductDetails({ productId, initialData }) {
const product = useBrowserQuery(`/api/products/${productId}`, {
initialData,
});
return <h1>{product.name}</h1>;
}initialData がある場合、React はサーバでコンポーネントを HTML にレンダーします。ない場合は、最も近い <Suspense> バウンダリのフォールバックを HTML に残します。ブラウザでは、useQuery が通常通りデータを取得したり、クライアント側のキャッシュから読み取ったりできます。
サーバでブラウザ専用レンダーの発生を通知させる
サーバレンダラに onBrowserBailout コールバックを渡すことで、ブラウザ専用レンダーが発生したことを通知させられます。React がサスペンスフォールバックを残してブラウザに引き継ぐ場合、サーバレンダラの onError コールバックや、hydrateRoot の onRecoverableError コールバックは呼び出されません。以下の例では理由も渡しており、報告されるエラーの cause として取得できます。
import { Suspense, use, useState } from 'react';
import { browser } from 'react-dom';
import { renderToPipeableStream } from 'react-dom/server';
function SavedDraft() {
use(browser(() => new Error('The saved draft is stored in localStorage.')));
const [draft] = useState(() => localStorage.getItem('draft') ?? '');
return <DraftEditor initialDraft={draft} />;
}
function App() {
return (
<Suspense fallback={<p>Loading saved draft...</p>}>
<SavedDraft />
</Suspense>
);
}
const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
pipe(response);
},
onBrowserBailout(error, errorInfo) {
logBrowserBailout(error, errorInfo);
}
});onBrowserBailout は 2 つの引数を受け取ります。
- ブラウザ専用レンダーが発生したことを説明する
Error。browserに理由を渡してあった場合、このエラーのcauseとして取得できます。 - ブラウザ専用レンダーがどこで発生したかを示す
componentStackを持つerrorInfoオブジェクト。
理由を生成する関数は、どのような値でも返せます。新しい Error を返す関数を使えば、原因を表すエラーに独自スタックを持たせつつ、ブラウザでは Error を作成しないようにできます。React は理由を HTML にシリアライズしません。
フォールバックを表示するサスペンスバウンダリがない場合、サーバでのレンダーは失敗します。React は onBrowserBailout ではなく、レンダラの通常のエラーコールバックを通じて失敗を報告します。
未完了のサーバレンダリングを中止してブラウザに引き継ぐ
サーバレンダリング API を直接呼び出している場合、まだ完了していないコンテンツの待機をやめて、ブラウザにレンダー完了を引き継がせることが可能です。サーバでのレンダーを中止する際に、browser の返した値を理由として渡してください。すると React は、まだ完了していないサスペンスバウンダリをフォールバックの状態のまま残し、そのコンテンツをブラウザでレンダーします。
import { browser } from 'react-dom';
import { renderToPipeableStream } from 'react-dom/server';
const { pipe, abort } = renderToPipeableStream(<App />, {
onShellReady() {
pipe(response);
setTimeout(() => {
abort(browser('The server render timed out.'));
}, 10000);
}
});中止の理由として browser の返した値を使っても、サーバレンダラの onError コールバックや、hydrateRoot の onRecoverableError コールバックは呼び出されません。代わりに、サーバレンダラは復帰した各サスペンスバウンダリを onBrowserBailout に報告します。
AbortSignal を受け取るサーバレンダリング API では、AbortController.abort に中止の理由として browser() を渡してください。