Feature-Sliced Design (FSD): 大規模なReactプロジェクトのための「分割統治」ソリューション

Development tutorial - IT technology blog
Development tutorial - IT technology blog

「スパゲッティコード」という悪夢と、転換点となった変化

50以上の画面と10人の開発者を抱えるSaaSプロジェクトを半年間運用した後、私は完全に行き詰まってしまいました。components/、hooks/、services/といった従来のディレクトリ構成(folder-by-type)は、最初は整っているように見えましたが、すぐに混沌とした状態に陥りました。何百ものファイルの中に隠れたロジックを探し出したり、他の部分を壊さずに機能を修正したりすることが、極めて困難な課題となったのです。

そこで私は、Feature-Sliced Design (FSD)に基づいて全体を再構築することに決めました。結果は驚くべきものでした。新人エンジニアのオンボーディング期間は2週間から4日間に短縮され、すべてが「あるべき場所」に配置されたことで、コードレビューのスピードも2倍になりました。FSDは単なるフォルダの命名規則ではありません。関心を科学的に分離するための「階層化(Layering)」の思考法なのです。

もしあなたが、ファイルAを修正した際、10階層も離れた場所にあるファイルBが突然エラーを吐いて発狂しそうになったことがあるなら、FSDこそが求めていた解決策です。

7つのレイヤー構造:FSD의 骨格

FSDではプロジェクトを7つの固定レイヤーに分割します。黄金律は極めてシンプルです。「**下位のレイヤーからのみインポート可能**」ということ。上位レイヤーからのインポートや、同じレイヤー内でのモジュール間のクロスインポートは厳禁です。

以下は、私が実際に導入して成功した構成図です:

src/
  ├── app/          # 全体設定 (Providers, Styles, Routing)
  ├── processes/    # (オプション) 複数のページにまたがる複雑なフロー
  ├── pages/        # ページ全体の構成 (Composition)
  ├── widgets/      # 大きなUIブロック (例:Header, Sidebar)
  ├── features/     # ユーザーアクション (Login, Search, AddToCart)
  ├── entities/     # コアとなるビジネスロジック (User, Product, Order)
  ├── shared/       # 共通コード (UI Kit, API client, Utils)

実践的な導入:焦らず一歩ずつ

最初からすべてを作り直す必要はありません。まずは最も低いレイヤーであるsharedから始めましょう。

  1. Shared: ビジネスロジックを持たない「純粋な」コンポーネントの集まりです。例:Button、Input、Axiosインスタンス。
  2. Entities: オブジェクトを定義する場所です。例えば、entities/userにはUserCardのUI、ユーザーデータの処理関数、および対応するRedux sliceが含まれます。
  3. Features: ユーザーが得られる価値に焦点を当てます。例:features/auth-by-email。

見分けるためのヒント:Entityは「これは何か?」(ユーザーデータ)という問いに答え、Featureは「ユーザーは何ができるか?」(ログイン)という問いに答えます。

Public API:厳格な門番

FSDでは、各サブディレクトリに**Public API**として機能するindex.tsを配置する必要があります。これは、外部からそのモジュール内部にアクセスするための唯一の窓口となります。

典型的なFeatureの構造は以下のようになります:

features/add-comment/
  ├── ui/               # 表示インターフェース
  ├── model/            # ロジック (States, Selectors)
  ├── lib/              # 内部ヘルパー関数
  └── index.ts          # 唯一のエクスポート窓口

index.tsでは、本当に必要なものだけをエクスポートします:

// features/add-comment/index.ts
export { AddCommentForm } from './ui/AddCommentForm';
export type { CommentSchema } from './model/types';

コードをクリーンでプロフェッショナルに保つために、tsconfig.jsonのパスエイリアス(path aliases)を活用しましょう。長い相対パスの代わりに@/を使用します:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

これにより、pages/article-detailsでのインポートは非常に簡潔になります:import { AddCommentForm } from '@/features/add-comment';。注意点として、Public APIをバイパスしてインポート(例:@/features/add-comment/ui/Form)してはいけません。カプセル化が壊れ、システムのメンテナンスが極めて困難になります。

ESLintをアーキテクチャの「警察」として活用する

理論は素晴らしいですが、チームが大きくなるとヒューマンエラーは避けられません。誰かが誤って2つのFeature間でクロスインポートをしてしまうかもしれません。これを防ぐために、私はeslint-plugin-boundariesを使用しています。

境界制御ツールのインストール:

npm install eslint-plugin-import eslint-plugin-boundaries --save-dev

この設定により、レイヤリング規則に違反した場合、IDE上で即座にエラーが通知されるようになります。例えば、entitiesがfeaturesに依存することを禁止します:

// 設定ルールの抜粋
"boundaries/element-types": [
  2,
  {
    "default": "disallow",
    "rules": [
      { "from": "entities", "allow": ["shared"] },
      { "from": "features", "allow": ["entities", "shared"] }
    ]
  }
]

6ヶ月の実践から得られた教訓

FSDはすべてのプロジェクトにおける「銀の弾丸」ではありません。画面が2〜3個しかない小規模なアプリにFSDを適用するのは、牛刀をもって鶏を割くのと同じで、無駄な時間を費やすことになります(オーバーエンジニアリング)。

しかし、大規模プロジェクトでは、以下の3つのことを覚えておいてください。まず、土台となるsharedレイヤーには十分な投資をすること。次に、新しい開発者のために必ずARCHITECTURE.mdを用意すること。最後に、あまりに教条的になりすぎず、ビジネスの特性に合わせて柔軟にレイヤーを調整することです。

FSDを導入すると、最初はコードを書くスピードが少し落ちるかもしれません。しかし、信じてください。1年後にメンテナンスのためにコードを読み返したとき、このように整理整頓されたコードを書いた自分自身に感謝することになるでしょう。

Share: