本文へ移動
uiify

アーキテクチャ

uiifyは5つのレイヤーをそれぞれ公開し、上位レイヤーはインポートしません。すべてに共通する原則は、コンポーネントがハイドレーション前に動作することです。

ハイドレーションまでの空白期間

ストリーミングSSRでは、初回描画からハイドレーションの完了までに空白期間があり、Suspenseでは遅れて届く境界ごとにその期間が生じます。その間、useStateのアコーディオンは表示されても操作できませんが、<details>のアコーディオンは初回描画から動作します。

同じアコーディオンを、<script>タグを一切使わずにレンダリングした2つのドキュメントがあります。`<details>`を使うものと`useState`を使うものを開くと、前者は開き、後者は開きません。ソースでも確認できます。

目標は、不要なレンダリング、プロバイダー全体への更新の波及、ライブラリのJavaScriptによる同期レイアウト処理をそれぞれゼロにすることです。状態はどこかで描画する必要があるため、レンダリング自体をゼロにするわけではありません。JavaScriptは標準でゼロとし、アクセシビリティに必要な部分にだけ最小限使います。

Tier

各コンポーネントは、ビルドが読み込むマニフェストにTierを宣言します。このマニフェストが、サイズ予算、RSCの純粋性を検査するlint、Playwrightのテストスイート、各コンポーネントページのTierバッジを決定します。

Tier予算JavaScriptが無効な場合意味
Tier 0
RSC — クライアントJavaScriptゼロ
0 kBJavaScript無効のテストスイートをすべて通過。状態はネイティブ要素が保持するか、そもそも状態を持ちません。useMemoやuseRefも含め、すべてのReactフックを禁止します。
Tier 1
RSCシェルと小さなクライアントアイランド
≤ 1 kB機能を制限し、コンポーネントごとに明記。状態は引き続きネイティブ要素が保持します。JavaScriptはARIAの同期や、ホバー意図の判定、自動終了などプラットフォームにない機能の追加にのみ使います。
Tier 2
クライアントコンポーネント — ハイドレーション前のSSR
コンポーネントごとハイドレーション前に意味のあるマークアップを描画。クライアントエントリーは1つです。サーバーは可能な限りネイティブ優先のマークアップを出力します。キーボードナビゲーション、フィルタリング、ARIAの同期はハイドレーション後に動作します。

Tierは移行にも使えます。判定条件をコンポーネント単位で有効にし、1つずつアーキテクチャを厳格にできます。

レイヤー構成

@gunkin/uiify/hooks       SSR-safe React hooks — the substrate
@gunkin/uiify/core        headless behaviour: focus scope, dismiss layers, anchoring
@gunkin/uiify/elements    native-mechanism wrappers — zero client JavaScript
@gunkin/uiify/components  unstyled components, RSC by default
@gunkin/uiify/styles      optional CSS design layer

下位レイヤーは上位レイヤーをインポートしません。最も厳格な@gunkin/uiify/elementsは、@gunkin/uiify/core/renderとReactの型だけをインポートできます。

elementsを公開レイヤーにする理由

多くのアプリは@gunkin/uiify/elementsを直接インポートしません。公開する理由は2つあります。

1つ目は、設計を検証できることです。コンポーネントページに*elements/dialogを土台に構築*とあれば、要素のページで内部の正確なマークアップとdata-*契約を確認できます。

2つ目は、組み合わせて使えることです。コンポーネントの構造がデザインに合わなければ、仕組みを作り直さず、同じ接続を使って独自の構造を組み立てられます。

このレイヤーに加わるモジュールは、5つの条件をすべて満たす必要があります。ネイティブ機構が1つであること、JavaScriptによる動作を持たないこと、@gunkin/uiify/componentsに利用箇所が2つ以上あること、CSSセレクターで状態を読めること、間接参照を増やすのではなくマークアップの重複を減らすことです。

モジュール名はUIの概念ではなく、details、dialog、popover、invokerなどプラットフォームの機能に由来します。コンポーネントと要素は同名にならないため、Modalはdialogを土台にしています。要素を見る

Tier 0でReact contextを禁止する理由

パーツ間の通信がReact contextに依存したままでは、状態をDOMに移しても意味がありません。Server Componentはcontextを使えないため、プロバイダーを共有するRootとItemは、マークアップがどれほどネイティブでもサーバーでレンダリングできません。

接続はシリアライズ後も維持する必要があるため、パーツはHTMLでつなぎます。グループ化には<details name>、ブラウザーが解決する参照にはcommandforとpopovertarget、IDにはpropsから導出した値を使います。useIdの値はcontextなしでは兄弟パーツに渡せません。

<Accordion id="faq">
  <Accordion.Item value="shipping">   {/* id="faq-shipping" */}

代わりに、ルートにはidが必要です。組み合わせが不要なら、コンポーネントがデータを受け取り、ツリー全体をレンダリングします。Server Componentを見る

このページは機械翻訳によるもので、レビューは未実施です。