📁

ReactのSPAで採用したディレクトリ構成

はじめに

シェルフィー株式会社でGreenfile.workの開発しているtaroと申します。
弊社では2022年11月頃からGreefile.work入退場の機能刷新プロジェクトを進めており、1月下旬に初回リリースをしました。
同プロジェクトでは設計段階からのリファクタリングを並行して進めており、この記事ではリファクタリングで採用したディレクトリ構成についてご紹介します。
リファクタリングの全体像はこちらをご覧ください。

技術スタックの概要

  • React(ClientSideRenderingのみ)
  • TypeScript
  • vite
  • styled-components
  • ボタンなどの汎用Componentは、Greenfile.workの全アプリで共通の内製UIライブラリを使用

以前のディレクトリ構成

以前はいわゆる技術ベースのディレクトリ構成でした。

課題

一番の課題だったのは、どんなファイルをどこに作るかが決まっていなかったことです。
コードを書く側は新しいファイルをどこに作ればいいのかが分からず人によってファイルを置く場所が違い、その結果コードを修正する側は考慮する範囲が見えにくくなっていました。
また1つの機能を修正する際に、pages, components, hooks, function, …といった広い範囲を修正する必要があり、この点も考慮する範囲を見えづらくしている要因でした。

シンプルで機能を関心事としたディレクトリ構成に移行

次は、上記の課題から考えた新しいディレクトリ構成とその思想です。

ディレクトリ構成の思想

具体的なディレクトリ構成を決める前に2つの思想を決めました。

シンプルにする

ディレクトリ構成においては、開発者がファイルを作る時とファイルを探す時に迷わないことが大切だと考えています。秩序の整ったルールが決まっていても、ルールが複雑だと理解が難しく結局迷ってしまいます。
そのため可能な限り「種類が少なく」「階層が浅い」シンプルなディレクトリ構成にしています。

技術ではなく機能を関心事とする

最近ではだいぶ主流となってきた機能を関心事とするディレクトリ構成です。
弊社でもほとんどの開発チケットは機能単位で切られ、実装者も機能単位で開発するため考慮する範囲のわかりやすさを重視し採用しました。

新しいディレクトリ構成

思想から直下のディレクトリは以下の7つに分けました。

pages

URLに対応するComponentファイルを置くディレクトリです。
他のComponentと区別するためComponent名は全てに統一しています。
またpagesのComponentはそのページのレイアウトや機能の全体像がひと目でわかるように互いに依存(pages内でネスト)させず、featuresとcomponentsからのみComponentをimportしています。

features

機能に依存するComponentやHookなどのファイルを置くディレクトリです。
機能に依存するファイルはComponentやHookだけでなく関数や型定義ファイルもfeaturesに置いています。
現在はフロントエンドにテストコードを導入できていないですが、今後入れる際には各機能のテストファイルもfeatures内に置く予定です。

components

機能に依存しないComponentファイルを置くディレクトリです。
Greenfile.workでは、全アプリで共通の内製UI Componentライブラリを使っているためcomponentsには入退場管理でのみ使用する機能に依存しないComponentファイルのみを置いています。
またそのComponentに依存するhookや関数などもcomponentsに置いています。

hooks

機能に依存しないhookファイルを置くディレクトリです。
そのhookに依存する関数などもhooksに置いています。

functions

機能に依存しない関数ファイルを置くディレクトリです。
その関数に依存する型定義ファイルなどもfunctionsに置いています。

types

機能に依存しない型定義ファイルを置くディレクトリです。

configs

外部ライブラリの設定ファイルを置くディレクトリです。
あくまで設定ファイルのみで、各機能に依存したファイルはfeaturesに置いています。

各ディレクトリの依存関係

configsを除く6つのディレクトリの依存関係は以下です。
各ディレクトリは左図のように上から下に依存しています。
  • ⭕pagesがfeaturesやcomponentsに依存
  • ❌componentsがfeaturesに依存
 
Atomicデザインやクリーンアーキテクチャのような厳密な依存関係を定義しているわけではなく、ページ→機能→非機能の自然な依存関係があるのみです。
components以下の非機能ファイルの依存関係も、関数を定義する時にhookやComponentをimportすることがないように、概念として自然なため実装時に意識することはほとんどありません。
普段意識するのは、機能に依存したファイルをhookや関数という技術的な種類で機能に依存しないディレクトリに作らないことくらいです。

新しくディレクトリを作る時に大切にしていること

直下の7つディレクトリ内のそれぞれの構成は、各スコープ内で揃っていて可読性が担保できていればOKとして特に具体的には決めず、新しくファイルやディレクトリを追加する時に大切にすることを2つだけ決めました。
  • 具体的でスコープの狭い名前をつける
    • 命名のスコープが広いと「ここに入れられそうだから入れちゃお!」といった感じで、ディレクトリの作成者が想定していないファイルが入ってきてしまう可能性があります。
      今はエディターの機能で簡単に一括置換できるので、最初は具体的でスコープの狭い命名にし必要に応じて命名を変えるようにしています。
  • 最初から階層化しない
    • 命名と同じく最初から階層化すると、ディレクトリの作成者が想定していないファイルが入ってきてしまう可能性があるので、階層化も必要になったタイミングでするようにしています。
      またディレクトリを作らずにファイルだけにすることも多いです。
      特にfeaturesディレクトリはファイル数が多くなり、つい階層構造が深くなってしまいがちなので注意してます。
      featuresの階層の深さは機能に依存したComponentの複雑さに比例します。
      そのため機能に依存しない汎用Componentや汎用Hookを上手く切り出して複雑さを下げることで、階層を浅く保ったまま機能数が増えるにつれて浅く横に広がるような運用をしていきたいと考えています。

改善点

最後にこれからの改善点です。

ルーティング

Greenfile.work入退場ではNext.jsを使っていないためファイルシステムのルーティングではなく、React Router(v5)でルーティングをしています。
React Routerはv6からを使って共通レイアウトを組めるなど、ルーティング設計の選択肢が増えるため、ルーティングに関するディレクトリの構成はReact Routerのバージョンをv6に上げてから検討する予定です。

まとめ

新しいディレクトリ構成への移行は完了していて、現在は移行+運用で約4ヶ月ほどが経ったところで今のところはディレクトリ構成で悩む時間はほぼなくなり順調に運用できていると感じています。
現状はファイル数もそこまで多くないためシンプルな構成で運用していますが、今後はアプリケーションの拡大とともに必要に応じて改善していきたいと考えています。
最後まで読んでいただきありがとうございました。

さいごに

シェルフィーではエンジニアを中心に積極的に採用をしております!
技術以外にも、「コンテック(Con-Tech)って何?」「面白い?」「働き方は?」といった気になるご質問すべてに正直にお答えいたします。
ぜひ一度お話しましょう!下記からご連絡ください◎