ぼくがかんがえた最強の React, Next.js ディレクトリ構成
React doesn’t have opinions on how you put files into folders.[1]
( React はディレクトリ構成に意見を持ちません )
Laravel[2]など一部言語やフレームワークでは推奨ディレクトリ構成が示されている一方、
このように、React は公式でディレクトリ構成に意見を持たないと明言しています。
そんな中 ゆめみパスポート の課題に取り組むにあたって設計やディレクトリ構成について調べたので、共有したいと思います。
この記事は以下の記事に強く影響を受けています。
詳しくディレクトリ構成について知りたい方は以下の記事を確認してみてください。
結論
src
├── app
│ ├── _components # ページ固有のコンポーネント
│ └── page.tsx
├── components # 汎用コンポーネント
│ ├── buttons
│ └── modals
├── contexts
├── features # 機能単位のディレクトリ
│ ├── prefecture # 都道府県(機能)に関するコード
│ │ ├── components
│ │ └── schemas
│ └── population
│ └── ...
└── lib
このようなトップレベルでは feature-based
、各 feature
ディレクトリ内では type-based
なディレクトリ構成を採用することで、
適切にコロケーションを行いつつ、各 feature
ディレクトリ内では技術的な関心の分離によって、ロジックとUIが分離されテストしやすい構成になります。
(各用語は後述)
〇〇-based
ディレクトリ構成において何に基づいてディレクトリを分けるかを表す言葉として「〇〇-based」という言葉が使われます。
ほかにも 「package by 〇〇」と呼ばれることもあります。
feature-based
機能単位でのディレクトリ構成を指します。
例えば features/prefecture
, features/population
などが該当します。
これは ビジネスドメイン などと同じような考え方です。
type-based
機能単位でのディレクトリ構成を指します。
例えば create-next-app
で生成される public
, app
, components
などが該当します。
また、Laravel[3]などで推奨されている MVC における Models
, Views
, Controllers
などもそれにあたります。
layer-based
レイヤー単位でのディレクトリ構成を指します。
レイヤードアーキテクチャーのような層に基づいてコードを分割するディレクトリ構成で、
アプリケーション、ドメイン、インフラのような層に基づいたディレクトリが該当します。
コロケーション(co-location)について
コロケーションとは 関連度の高いコードは近くに置こう という考え方です。
また、コロケーションとは、一緒に変化するものは近くに置こう という考え方だと言い換えることができます。
コード変更の一般的なシナリオでは prefecture
のスキーマだけを変更するということは考えづらく、スキーマを変更するときUIなどほかの関連する部分も変更するはずです。
具体的には、技術的な関心でディレクトリを分けていると以下のような問題が生じます:
- 変更の時いろいろなディレクトリを行ったり来たりしないといけない
- コードリーディングの時に保持しなければいけないコンテキストが増える
1 は自明だと思いますが、2 に関してはある機能がどのように動作しているか読み解く場合、
トップレベルで type-based
なディレクトリ構成になっていると src
配下のすべてのディレクトリに対して、対象の機能に関するコードがどこにあるかという情報を保持する必要があります。
しかし、feature-based
なディレクトリ構成にすることで、対象の機能に関するコードは features/hoge
に集合しており、それ以下のディレクトリを探索するだけで済みます。
このように規模が大きくなるほどにコロケーションのメリットも大きくなると思います。
ディレクトリ構成のネスト
ここまででなんとなく feature-based
がよさそうと思ってきたと思います。
しかし、feature-based
なディレクトリ構成には一つ欠点があります。
機能に関するコードは一つのディレクトリにフラットに配置するには膨大だということです。
そこで、feature-based
な各ディレクトリ配下に type-based
または layer-based
なディレクトリを構築します。
これにより、コロケーションのメリットを享受しつつ、技術的な関心によってコードを分離することで、UIとロジックが分離され テスト可能になります。
今回は feature-based
, type-based
のネストを採用しましたが、規模の大きいアプリケーション、複雑なロジックをもつアプリケーションでは feature-based
, layer-based
のネストも選択肢だと思います。
まとめ
- コロケーションが大事
- 読み解きやすいコードベースに保つには
feature-based
がよさそう - 書きやすいコードベースには
type-based
,layer-based
がよさそう - 両方のメリットを享受するためにネストの選択肢がある
[余談] テストファイルの配置に関して
src/hoge.ts
のテストを書く場合、tests/unit/hoge.ts
と書く派閥と src/hoge.test.ts
と書く派閥があると思います。
これもコロケーションの観点から後者のようにテスト対象のコードと同じ階層に配置するべきだと思います。
そして e2e テストなどすべてのコードを横断するようなテストコードは e2e/
などに配置します。
参考文献
Discussion