第7章 ディレクトリ構成と共有
ページ単位で分ける
Section titled “ページ単位で分ける”実装では、まずページ単位でディレクトリを分けます。
Scaffold、Bridge、Island、Component は、原則としてそのページの内部から始めます。
pages/├─ project/│ ├─ ProjectScaffold│ ├─ bridges/│ │ └─ TaskSelectionBridge│ ├─ islands/│ │ ├─ KanbanIsland│ │ └─ TaskDetailIsland│ └─ components/│ ├─ TaskCard│ └─ ColumnHeader│└─ settings/ ├─ SettingsScaffold └─ islands/ ├─ AccountIsland └─ AppearanceIsland図の表記は、末尾が / のものはディレクトリ、それ以外はファイルです。ファイルの拡張子は環境によって違う(.tsx、.dart、.vue、.swift など)ため省いています。ProjectScaffold は1つのファイル、islands/ はディレクトリです。
Island や Component が1ファイルに収まらなくなったら、同名のディレクトリへ変えて構いません。
islands/└─ KanbanIsland/ ├─ KanbanIsland ← 入口となるファイル ├─ KanbanState └─ useDragAndDropsettings/ に bridges/ と components/ がないのは、設定画面では Island 同士の協調が必要なく、ページ固有の Component もまだ存在しないからです。
すべてのページに同じディレクトリを機械的に作る必要はありません。 空のディレクトリは、そこに何かを置くべきだという誤った示唆になります。
重要なのはディレクトリ構成そのものではなく、どのページの責務として存在するかを明確にすることです。
識別子には次の規則を使います。
| 単位 | 接尾辞 | 例 |
|---|---|---|
Scaffold |
付ける | ProjectScaffold、TimelineScaffold |
Bridge |
付ける | TaskSelectionBridge、PostSelectionBridge |
Island |
付ける | KanbanIsland、TimelineIsland |
Component |
付けない | TaskCard、SearchField |
Part |
付けない | Card、Badge |
Scaffold / Bridge / Island に接尾辞を付けるのは、名前から責務の所在が分かる必要があるからです。状態を追うとき、「この値はどこが持っているのか」を名前だけで判断できます。
Component / Part に付けないのは、TaskCardComponent のように書いても情報が増えないためです。これらは名前そのものが役割を表しています。
共有領域への昇格
Section titled “共有領域への昇格”Component 以上の要素は、まずページローカルから始めます。
複数ページから実際に利用されるようになった段階で、共有領域へ昇格させます。
Page Local ↓複数ページから利用される ↓Shared「実際に」利用されるまで昇格させないのがこの規則の要点です。「これは他でも使いそうだ」という予測で共有領域へ置くと、まだ1箇所からしか使われていないのに、変更のたびに他ページへの影響を考えることになります。
昇格の目安は、2つ目のページから使われたときです。1箇所しか使っていないうちは、そのページの都合で自由に変えられます。2箇所目が現れた時点で、初めて共通の仕様を考える理由が生まれます。
この昇格の線は、ライブラリになりうるものと、アプリ固有のものを分けています。Part が最初から共有領域に置けて、Island の共有には注意が要るのは、そのためです(→ 第5章 既存の設計手法との比較)。
再利用性をどこまで先回りするか
Section titled “再利用性をどこまで先回りするか”結論から書きます。先回りしません。2つ目の利用が現れてから考えます。
再利用できる形にしておくとは、「どう使われるか分からないものを、どう使われてもよい形にする」ということです。使われ方が1つしか分かっていない段階では、何を可変にすべきかも決められません。結果として、使われていない引数と、通らない分岐が増えます。
2箇所目が現れたときに初めて、2つの使われ方の差分が見えます。差分が分かってから可変にすれば、必要なものだけが引数になります。
| 単位 | 先回りしてよいか |
|---|---|
Part |
してよい。 汎用的なものは最初から共有領域へ |
Component |
しない。2つ目のページから使われた時点で昇格 |
Island |
しない。昇格そのものにも注意が要る(後述) |
Bridge / Scaffold |
そもそも共有しない。ページ固有のもの |
Part だけが先回りしてよい
Section titled “Part だけが先回りしてよい”Part が例外なのは、表現しか担当しないため、使われ方の幅が最初から狭いからです。Text に渡すのは文字列とスタイルだと、2箇所目を待たなくても分かります。ページ固有の文脈にも依存しにくいため、汎用的な Part は最初から共有領域に配置できます。
shared/└─ parts/ ├─ Text ├─ Icon ├─ Card ├─ Badge └─ Divider一方、ドメイン固有の表現まで無理に共有する必要はありません。「案件のステータスを色付きの丸で表す」といった表現は、その色とステータスの対応がドメインの知識であり、他のページでは意味が変わります。
汎用的な表現→ shared/parts
ページ・ドメイン固有の表現→ Page Local昇格先の構成
Section titled “昇格先の構成”共有領域は、単位ごとにディレクトリを分けます。
shared/├─ parts/│ ├─ Text│ ├─ Icon│ ├─ Card│ ├─ Badge│ └─ Divider├─ components/│ ├─ ConfirmDialog│ └─ DateRangePicker└─ islands/ └─ NotificationCenterIslandshared/components/ は、操作と振る舞いを持つが、特定のページに依存しないものです。日付範囲の選択や確認ダイアログのように、どのページで使っても意味が変わりません。
shared/islands/ は慎重に使ってください。 Island を共有するということは、その機能が持つ状態と、扱っているドメインの知識ごと共有するということです。通知センターのように、どのページでも同じ意味を持つ機能に限られます。「一覧を表示する」といった形が似ているだけの機能を共有すると、ページごとの差分を引数で切り替えることになり、Island の内部が分岐だらけになります。
Component 以上はページローカルから始め、再利用が明確になった段階で共有領域へ昇格します。