レイヤー
レイヤーは、Feature-Sliced Designにおける組織階層の最初のレベルです。その目的は、コードを責任の程度やアプリ内の他のモジュールへの依存度に基づいて分離することです。各レイヤーは、コードにどれだけの責任を割り当てるべきかを判断するための特別な意味を持っています。
合計で7つのレイヤーがあり、責任と依存度が最も高いものから最も低いものへと配置されています。
- App (アップ)
- Processes (プロセス、非推奨)
- Pages (ページ)
- Widgets (ウィジェット)
- Features (フィーチャー)
- Entities (エンティティ)
- Shared (シェアード)
プロジェクトにすべてのレイヤーを使用する必要はありません。プロジェクトに価値をもたらすと思う場合のみ追加してください。通常、ほとんどのフロントエンドプロジェクトには、少なくともShared、Page、Appのレイヤーがあります。
実際には、レイヤーは小文字の名前のフォルダーです(例えば、📁 shared
、📁 pages
、📁 app
)。新しいレイヤーを追加することは推奨されていません。なぜなら、その意味は標準化されているからです。
レイヤーに関するインポートルール
レイヤーは スライス で構成されており、これは非常に凝集性の高いモジュールのグループです。スライス間の依存関係は、レイヤーに関するインポートルールによって規制されています。
スライス内のモジュール(ファイル)は、下位のレイヤーにある他のスライスのみをインポートできます。
例えば、フォルダー 📁 ~/features/aaa
は「aaa」という名前のスライスです。その中のファイル ~/features/aaa/api/request.ts
は、📁 ~/features/bbb
内のファイルからコードをインポートすることはできませんが、📁 ~/entities
や 📁 ~/shared
からコードをインポートすることができ、例えば ~/features/aaa/lib/cache.ts
などの同じスライス内の隣接コードもインポートできます。
AppとSharedのレイヤーは、このルールの例外です。これらは同時にレイヤーとスライスの両方です。スライスはビジネスドメインによってコードを分割しますが、これらの2つのレイヤーは例外です。なぜなら、Sharedはビジネスドメインを持たず、Appはすべてのビジネスドメインを統合しているからです。
実際には、AppとSharedのレイヤーはセグメントで構成されており、セグメントは自由に互いにインポートできます。
レイヤーの定義
このセクションでは、各レイヤーの意味を説明し、どのようなコードがそこに属するかの直感を得るためのものです。
Shared
このレイヤーは、アプリの残りの部分の基盤を形成します。外部世界との接続を作成する場所であり、例えばバックエンド、サードパーティライブラリ、環境などです。また、特定のタスクに集中した自分自身のライブラリを作成する場所でもあります。
このレイヤーは、Appレイヤーと同様に、スライスを含みません。スライスはビジネスドメインによってレイヤーを分割することを目的としていますが、Sharedにはビジネスドメインが存在しないため、Shared内のすべてのファイルは互いに参照し、インポートすることができます。
このレイヤーで通常見られるセグメントは次のとおりです。
📁 api
— APIクライアントおよび特定のバックエンドエンドポイントへのリクエストを行う関数📁 ui
— アプリケーションのUIキット このレイヤーのコンポーネントはビジネスロジックを含むべきではありませんが、ビジネスに関連することは許可されています。例えば、会社のロゴやページレイアウトをここに置くことができます。UIロジックを持つコンポーネントも許可されています(例えば、オートコンプリートや検索バー)📁 lib
— 内部ライブラリのコレクション このフォルダーはヘルパーやユーティリティとして扱うべきではありません(なぜこれらのフォルダーがしばしばダンプに変わるか)。このフォルダー内の各ライブラリは、日付、色、テキスト操作など、1つの焦点を持つべきです。その焦点はREADMEファイルに文書化されるべきです。チームの開発者は、これらのライブラリに何を追加でき、何を追加できないかを知っているべきです📁 config
— 環境変数、グローバルフィーチャーフラグ、アプリの他のグローバル設定📁 routes
— ルート定数、またはルートをマッチさせるためのパターン📁 i18n
— 翻訳のセットアップコード、グローバル翻訳文字列
さらにセグメントを追加することは自由ですが、これらのセグメントの名前は内容の目的を説明するものでなければなりません。例えば、components
、hooks
、types
は、コードを探しているときにあまり役立たないため、悪いセグメント名です。
Entities
このレイヤーのスライスは、プロジェクトが扱う現実世界の概念を表します。一般的には、ビジネスがプロダクトを説明するために使用する用語です。例えば、SNSは、ユーザー、投稿、グループなどのビジネスエンティティを扱うかもしれません。
エンティティスライスには、データストレージ(📁 model
)、データ検証スキーマ(📁 model
)、エンティティ関連のAPIリクエスト関数(📁 api
)、およびインターフェース内のこのエンティティの視覚的表現(📁 ui
)が含まれる場合があります。視覚的表現は、完全なUIブロックを生成する必要はなく、アプリ内の複数のページで同じ外観を再利用することを主に目的としています。異なるビジネスロジックは、プロップスやスロットを通じてそれに付加されることがあります。
エンティティの関係
FSDにおけるエンティティはスライスであり、デフォルトではスライスは互いに知ることができません。しかし、現実の世界では、エンティティはしばしば互いに相互作用し、一方のエンティティが他のエンティティを所有、または含むことがあります。そのため、これらの相互作用のビジネスロジックは、フィーチャーやページのような上位のレイヤーに保持されるのが望ましいです。
一つのエンティティのデータオブジェクトが他のデータオブジェクトを含む場合、通常はエンティティ間の接続を明示的にし、@x
表記を使用してスライスの隔離を回避するのが良いアイデアです。理由は、接続されたエンティティは一緒にリファクタリングする必要があるため、その接続を見逃すことができないようにするのが最善です。
例:
import type { Song } from "entities/song/@x/artist";
export interface Artist {
name: string;
songs: Array<Song>;
}
export type { Song } from "../model/song.ts";
@x
表記の詳細については、クロスインポートの公開APIセクションを参照してください。
Features
このレイヤーは、アプリ内の主要なインタラクション、つまりユーザーが行いたいことを対象としています。これらのインタラクションは、ビジネスエンティティを含むことが多いです。
アプリのフィーチャーレイヤーを効果的に使用するための重要な原則は、すべてのものがフィーチャーである必要はないということです。何かがフィーチャーである必要がある良い指標は、それが複数のページで再利用されるという事実です。
例えば、アプリに複数のエディターがあり、すべてにコメントがある場合、コメントは再利用されるフィーチャーです。スライスはコードを迅速に見つけるためのメカニズムであり、フィーチャーが多すぎると重要なものが埋もれてしまいます。
理想的には、新しいプロジェクトに入ったとき、既存のページやフィーチャーを見ると、アプリの機能性が分かります。何がフィーチャーであるべきかを決定する際には、プロジェクトの新参者が重要なコードの大きな領域を迅速に発見できるように最適化してください。
フィーチャーのスライスには、インタラクションを実行するためのUI(例えばフォーム、📁 ui
)、アクションを実行するために必要なAPI呼び出し(📁 api
)、検証および内部状態(📁 model
)、フィーチャーフラグ(📁 config
)が含まれる場合があります。
Widgets
ウィジェットレイヤーは、大きな自己完結型のUIブロックを対象としています。ウィジェットは、複数のページで再利用される場合や、所属するページにある複数の大きな独立したブロックの一つである場合に最も便利です。
UIのブロックがページの大部分を構成し、再利用されない場合、それはウィジェットであるべきではなく、代わりにそのページ内に直接配置するべきです。
ネストされたルーティングシステム(Remixのルーターのような)を使用している場合、ウィジェットレイヤーを、フラットなルーティングシステムがページレイヤーを使用するのと同じように使用することが役立つかもしれません。それは関連データの取得、ローディング状態、エラーバウンダリを含む完全なルーターブロックを作成するためです。
同様に、このレイヤーにページレイアウトを保存することもできます。
Pages
ページは、ウェブサイトやアプリケーションを構成するものです(スクリーンやアクティビティとも呼ばれます)。通常、1ページは1つのスライスに対応しますが、非常に似たページが複数ある場合、それらを1つのスライスにまとめることができます。例えば、登録フォームとログインフォームです。
チームがナビゲートしやすい限り、ページスライスに配置できるコードの量に制限はありません。ページ上のUIブロックが再利用されない場合、それをページスライス内に保持することは完全に問題ありません。
ページスライスには、通常、ページのUIやローディング状態、エラーバウンダリ(📁 ui
)、データの取得や変更リクエスト(📁 api
)が含まれます。ページが専用のデータモデルを持つことは一般的ではなく、状態の小さな部分はコンポーネント自体に保持されることがあります。
Processes
このレイヤーは非推奨です。現在の仕様では、これを避け、その内容をFeaturesやAppに移動することを推奨しています。
プロセスは、マルチページインタラクションのための逃げ道です。
このレイヤーは意図的に未定義のままにされています。ほとんどのアプリケーションはこのレイヤーを使用せず、ルーターやサーバーレベルのロジックをAppレイヤーに保持するべきです。このレイヤーは、Appレイヤーが大きくなりすぎてメンテナンスが困難になった場合にのみ使用することを検討してください。
App
アプリ全体に関するあらゆるもの、技術的な意味(例えば、コンテキストプロバイダー)やビジネス的な意味(例えば、分析)を含みます。
このレイヤーには通常、スライスは含まれず、Sharedと同様に、セグメントが直接存在します。
このレイヤーで通常見られるセグメントは次のとおりです。
📁 routes
— ルーターの設定📁 store
— グローバルストアの設定📁 styles
— グローバルスタイル📁 entrypoint
— アプリケーションコードへのエントリポイント、フレームワーク固有