アーキテクチャ
06. ブラウザーの遷移
遷移先の読み込みから画面とブラウザー履歴の更新まで、ページ遷移の実装を読み解きます。
Reactのcommit、ブラウザー履歴のcommit、Flightの完了は別の出来事です。 遅延データのストリーミング中にもルートを表示できるため、遷移は表示中のツリーと未完了のリソースを両方追跡します。
hydrationは HTMLに埋め込んだFlight から始まります。 後続の応答も同じ サーバー生成のルートツリーモデル を運びますが、ドキュメントを新しく作らず既存のReactルートを更新します。
初期ツリーを確立し、クライアント遷移を選ぶ
client/application.ts の activateBrowser は初期Flightペイロードを読み、ReactDOMRenderer.hydrate を呼びます。 hydrationはReactへ formState を渡し、layout effectがツリーとstate更新関数で BrowserRenderer を初期化するまで待ちます。 その後でrefresh、Server Functionのcallback、対応していればクライアントルーターを登録します。
client/browser-capabilities.ts は、window.navigation と window.NavigationPrecommitController の両方がある場合だけクライアントルーティングを選びます。 それ以外では、hydrate済みのページもドキュメント遷移を使います。 両APIがあっても、一部のイベントはブラウザーに任せます。
packages/core/src/client/navigation-routing.ts の抜粋const ReactTransitionNavigationInfo = "react-transition";
export const NativeDocumentNavigationInfo = "effront-native-document";
export const isRoutedNavigation = (event: NavigateEvent) =>
event.canIntercept &&
!event.hashChange &&
event.downloadRequest === null &&
event.formData === null &&
event.info !== ReactTransitionNavigationInfo &&
event.info !== NativeDocumentNavigationInfo &&
event.navigationType !== "reload";hashのみの変更、download、フォーム、reload、二つのマーカー付き遷移はinterceptしません。 ドキュメント用のマーカーは、Effrontのフォールバック読み込みの再interceptを防ぎます。
browserMain は Effect.scoped と Effect.never でサービスと購読を維持します。 初期Flightやhydration開始時の失敗は、ブラウザーの失敗画面を表示します。 React描画中のエラーはレンダラーのError Boundaryが扱います。
ルートを取得するか、ブラウザーに処理を戻す
client/route-loader.ts は履歴遷移で、移動先entryのIDを使ってキャッシュ済みツリーを再利用します。 それ以外の遷移とキャッシュミスでは、FlightClient.load が Accept: text/x-component を付けてGETします。 応答ごとのScopeは、初期ペイロードのデコード後も存続できます。
- ドキュメントへのフォールバック:非2xxまたはFlight以外の応答は
Documentになります。 ルーターは応答を解放し、要求した移動先をドキュメントとして読み込みます。 - 読み込みエラー:通信失敗、解決済みURLの欠落や不正、デコード失敗は
FlightLoadErrorになり、理由はそれぞれRequestFailed、UnexpectedResponse、DecodeFailedです。 - Flight:結果にはペイロード、
completed、release、resolvedUrlが含まれます。 ルートツリーのデコード完了は、ストリームの完了を意味しません。
公開前に client/client-router.ts が解決済みの移動先を検査します。 originが異なればドキュメント遷移が必要です。 同じoriginでもURLが変わる場合、履歴遷移かprecommit controllerがない状況ではドキュメントを置き換えます。 それ以外では、Reactのcommit後にcontrollerでredirectできます。 要求したhashを保つのは、解決済みURLにhashがなく、origin・path・queryが一致するときだけです。
描画の予約とcommitを区別する
非同期のTransition Actionが BrowserEffectRunner を通してルートを読み込みます。 その後、内側のTransitionがツリーを公開しますが、公開はcommitではありません。
packages/core/src/client/client-router.ts の抜粋 const rendererNavigation = yield* Effect.sync(() => {
let navigation!: BrowserRendererNavigation;
startTransition(() => {
const fromIndex =
navigationApi.getTransition()?.from.index ??
navigationApi.getCurrentEntry()?.index ??
null;
for (const type of getNavigationTransitionTypes(event, fromIndex)) {
addTransitionType(type);
}
for (const type of linkTransitionTypes) {
addTransitionType(type);
}
navigation = browserRenderer.navigate(command.resource.routeTree);
});
return navigation;
});browserRenderer.navigate はstate更新を予約し、三つのライフサイクル操作を返します。
committedは、client/react-dom-renderer.tsxのlayout effectがbrowserRenderer.commit(render)を呼ぶと解決します。retiredは、置き換えのcommitでツリーを保持する必要がなくなると解決します。discardは、保留中の描画に対して現在のツリーの復元を依頼し、退役を待ちます。
キャンセル可能なイベントでは、event.intercept が precommitHandler を使います。 Reactのcommitを待ち、可能なredirectを適用し、履歴のcommitを記録するcallbackを addHandler に登録します。 キャンセル不能な履歴遷移は通常のhandlerを使うため、同じように履歴を遅らせられません。 この経路で準備が失敗するとドキュメントをreloadします。
Transition typeは遷移の種類と、判別できる場合には方向を表します。 リンクによるpushとreplaceは、重複と予約済みtypeを除いた data-effront-transition-types の値を加えられます。 このラベルはcommitや寿命の規則を変えません。 アプリケーションでの利用方法は クライアントナビゲーションとページ遷移 を参照してください。
完了したルートを保存し、不要になった描画を退役させる
処理中の候補は、表示中の遷移とは別に Loading → Publishing → Rendering を進みます。 generationのsymbolとAbortControllerにより、新しい遷移は表示中のツリーをすぐ解放せずに、保留中の処理をキャンセルできます。
古い読み込みは公開せず解放します。 予約済みの描画はdiscardし、退役後に解放します。BrowserRenderer は、表示中のツリーと、復元要求を含む保留中の公開が参照するツリーを保持します。 そのため、遷移のabortは描画の退役と同じではありません。 未公開または退役済みのcommitや不正なライフサイクル遷移は TypeError になります。
新たに読み込んで表示したルートのキャッシュには、履歴のcommitとFlightの正常完了の両方が必要です。NavigationEntryState と NavigationFlightState が独立に追跡します。
- 履歴が先:Flightを待ち、キャッシュして解放します。
- Flightが先:応答を解放し、履歴entryが分かるまでキャッシュ用callbackを保持します。
- ストリームの失敗:キャッシュせずに解放します。
新しいgenerationが始まっていても、描画の退役はそのリソースを解放します。 キャッシュが保持するのはツリーであり、開いた応答ストリームではありません。RouteLoader は破棄された履歴entryを削除し、refresh時にキャッシュのMapを置き換えて、遅れたcallbackによる新キャッシュへの書き込みを防ぎます。Server Functionの応答 も、同じレンダラーのライフサイクルで現在のページを更新します。