コンテンツにスキップ

第7章 ディレクトリ構成と共有

実装では、まずページ単位でディレクトリを分けます

ScaffoldBridgeIslandComponent は、原則としてそのページの内部から始めます。

pages/
├─ project/
│ ├─ ProjectScaffold
│ ├─ bridges/
│ │ └─ TaskSelectionBridge
│ ├─ islands/
│ │ ├─ KanbanIsland
│ │ └─ TaskDetailIsland
│ └─ components/
│ ├─ TaskCard
│ └─ ColumnHeader
└─ settings/
├─ SettingsScaffold
└─ islands/
├─ AccountIsland
└─ AppearanceIsland

図の表記は、末尾が / のものはディレクトリ、それ以外はファイルです。ファイルの拡張子は環境によって違う(.tsx.dart.vue.swift など)ため省いています。ProjectScaffold は1つのファイル、islands/ はディレクトリです。

IslandComponent が1ファイルに収まらなくなったら、同名のディレクトリへ変えて構いません。

islands/
└─ KanbanIsland/
├─ KanbanIsland ← 入口となるファイル
├─ KanbanState
└─ useDragAndDrop

settings/bridges/components/ がないのは、設定画面では Island 同士の協調が必要なく、ページ固有の Component もまだ存在しないからです。

すべてのページに同じディレクトリを機械的に作る必要はありません。 空のディレクトリは、そこに何かを置くべきだという誤った示唆になります。

重要なのはディレクトリ構成そのものではなく、どのページの責務として存在するかを明確にすることです。

識別子には次の規則を使います。

単位 接尾辞
Scaffold 付ける ProjectScaffoldTimelineScaffold
Bridge 付ける TaskSelectionBridgePostSelectionBridge
Island 付ける KanbanIslandTimelineIsland
Component 付けない TaskCardSearchField
Part 付けない CardBadge

Scaffold / Bridge / Island に接尾辞を付けるのは、名前から責務の所在が分かる必要があるからです。状態を追うとき、「この値はどこが持っているのか」を名前だけで判断できます。

Component / Part に付けないのは、TaskCardComponent のように書いても情報が増えないためです。これらは名前そのものが役割を表しています。

Component 以上の要素は、まずページローカルから始めます。

複数ページから実際に利用されるようになった段階で、共有領域へ昇格させます。

Page Local
複数ページから利用される
Shared

「実際に」利用されるまで昇格させないのがこの規則の要点です。「これは他でも使いそうだ」という予測で共有領域へ置くと、まだ1箇所からしか使われていないのに、変更のたびに他ページへの影響を考えることになります。

昇格の目安は、2つ目のページから使われたときです。1箇所しか使っていないうちは、そのページの都合で自由に変えられます。2箇所目が現れた時点で、初めて共通の仕様を考える理由が生まれます。

この昇格の線は、ライブラリになりうるものと、アプリ固有のものを分けています。Part が最初から共有領域に置けて、Island の共有には注意が要るのは、そのためです(→ 第5章 既存の設計手法との比較)。

再利用性をどこまで先回りするか

Section titled “再利用性をどこまで先回りするか”

結論から書きます。先回りしません。2つ目の利用が現れてから考えます。

再利用できる形にしておくとは、「どう使われるか分からないものを、どう使われてもよい形にする」ということです。使われ方が1つしか分かっていない段階では、何を可変にすべきかも決められません。結果として、使われていない引数と、通らない分岐が増えます。

2箇所目が現れたときに初めて、2つの使われ方の差分が見えます。差分が分かってから可変にすれば、必要なものだけが引数になります。

単位 先回りしてよいか
Part してよい。 汎用的なものは最初から共有領域へ
Component しない。2つ目のページから使われた時点で昇格
Island しない。昇格そのものにも注意が要る(後述)
Bridge / Scaffold そもそも共有しない。ページ固有のもの

Part が例外なのは、表現しか担当しないため、使われ方の幅が最初から狭いからです。Text に渡すのは文字列とスタイルだと、2箇所目を待たなくても分かります。ページ固有の文脈にも依存しにくいため、汎用的な Part は最初から共有領域に配置できます。

shared/
└─ parts/
├─ Text
├─ Icon
├─ Card
├─ Badge
└─ Divider

一方、ドメイン固有の表現まで無理に共有する必要はありません。「案件のステータスを色付きの丸で表す」といった表現は、その色とステータスの対応がドメインの知識であり、他のページでは意味が変わります。

汎用的な表現
→ shared/parts
ページ・ドメイン固有の表現
→ Page Local

共有領域は、単位ごとにディレクトリを分けます。

shared/
├─ parts/
│ ├─ Text
│ ├─ Icon
│ ├─ Card
│ ├─ Badge
│ └─ Divider
├─ components/
│ ├─ ConfirmDialog
│ └─ DateRangePicker
└─ islands/
└─ NotificationCenterIsland

shared/components/ は、操作と振る舞いを持つが、特定のページに依存しないものです。日付範囲の選択や確認ダイアログのように、どのページで使っても意味が変わりません。

shared/islands/ は慎重に使ってください。 Island を共有するということは、その機能が持つ状態と、扱っているドメインの知識ごと共有するということです。通知センターのように、どのページでも同じ意味を持つ機能に限られます。「一覧を表示する」といった形が似ているだけの機能を共有すると、ページごとの差分を引数で切り替えることになり、Island の内部が分岐だらけになります。

Component 以上はページローカルから始め、再利用が明確になった段階で共有領域へ昇格します。