はじめに
本記事はAlan Alickovic氏のbulletproof-reactを参考にして学習します。
かなり初学者向けの説明で、正確性は低めなことにご留意ください。
ディレクトリ設計は何のためにあるか
結論から言うと、コードを探して変更・管理しやすくするためです。
例えば、ユーザー一覧を表示するアプリを考えます。
src/
└── App.tsx # 画面・データ取得・ユーザー一覧の表示を全て書く
規模の小さいうちはこれでも把握できますが、ユーザー情報の編集や請求管理等の機能を追加するとApp.tsxのコード量が膨大になります。
すると、変更したい箇所のコードを探すのも一苦労です。
そこで、まずはユーザー一覧の表示を別ファイルへ分けます。
src/
├── App.tsx # 画面全体を組み立てる
└── UserTable.tsx # ユーザー一覧の表を表示する
こうすることで、「一覧の見た目だけを変えたい」と思った時、UserTable.tsxだけに着目して修正をかけることができます。
実際の構成
モノレポを前提とした構成について学びます。
モノレポ:
複数の関連するコードを1つのリポジトリで管理するアプローチ。
今回はfrontendとbackendを同じリポジトリで管理します。
👇全体構造はこんな感じ
project/
├── apps/
│ ├── web/ # フロントエンド:React
│ │ ├── public/ # そのまま配信する静的ファイル
│ │ ├── src/
│ │ │ ├── app/ # 画面・ルーティングの組み立て
│ │ │ ├── features/ # 業務機能ごとのUI・通信
│ │ │ ├── components/ # アプリ共通のUI
│ │ │ ├── lib/ # ライブラリなどの共通設定
│ │ │ ├── main.tsx # Reactの起動・HTMLへの接続
│ │ │ └── index.css # 全体のCSS・Tailwind・テーマ
│ │ ├── index.html # Reactの画面を表示するHTML
│ │ ├── package.json # フロントの依存関係・コマンド
│ │ └── tsconfig.json # フロントのTypeScript設定
│ │
│ └── api/ # バックエンド:Hono
│ ├── src/
│ │ ├── features/ # 業務機能ごとのAPI・処理
│ │ ├── middleware/ # 認証・ログなど
│ │ ├── db/ # DB接続・定義
│ │ ├── app.ts # Honoアプリの組み立て
│ │ └── index.ts # 起動・公開
│ ├── package.json # APIの依存関係・コマンド
│ └── tsconfig.json # APIのTypeScript設定
│
├── packages/ # 共有パッケージ。必要になったら追加
├── package.json # リポジトリ全体のコマンドなど
└── README.md # 開発手順
詳細なフォルダや設定ファイルは省略しています。
フロントエンド
src/配下のフォルダを詳しく見ていきます。
app/:画面とアプリ全体の組み立て
URLに応じた画面の切り替えや、アプリ全体で使う機能の設定を置きます。
src/
└── app/
├── routes/ # 各ページを構成するコンポーネント
├── router.tsx # URLと表示するページの対応の設定
├── provider.tsx # データ取得ライブラリ等の設定
└── index.tsx # Providerとルーターを組み合わせる
routes/とrouter.tsxの違い
routes/には「表示する画面」、router.tsxには「どのURLでその画面を表示するか」を書きます。
👇例
routes/内のページ
見出しや一覧の部品を組み合わせて画面を作る
router.tsx
/users → ユーザー一覧ページ
provider.tsxって何やるの
どの画面でも使いたい共通の機能を、まとめて用意するファイルです。
例えば、TanStack Queryでは取得データを管理するQueryClientを用意して親(Provider)が利用できるようにします。
後述するlib/は「失敗したら再試行するか」等の処理の置き場所、provider.tsxはReactの画面へ共通の値(queryClientとか)を渡すためのファイルといった感じです。
features/:業務機能ごとにコードをまとめる
特定の業務機能に関するUIやAPI通信をまとめます。
bulletproofだと👇のように分かれています。
features/
├── auth/ # 認証
├── comments/ # コメント
├── discussions/ # ディスカッション
├── teams/ # チーム管理
└── users/ # ユーザー管理
機能の中では必要に応じて役割を分けます。
例えば、discussion/は次の構成になっています。
discussions/
├── api/ # APIへの取得・更新処理
└── components/ # この機能専用のUI
api/の補足
api/には、フロントからバックへのデータの取得や更新を依頼するコードを置きます。
具体的には「どこへ依頼するか・何を依頼するか」等です。
👇例
export async function getUsers() {
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error("ユーザー一覧を取得できませんでした");
}
return response.json();
}
app/とfeatures/の違い
app/は画面を組み立て、features/は業務機能を実装します。
例えば、ユーザー一覧ページはapp/routes、そのページで使うユーザー一覧の部品や取得する処理はfeatures/users/に置きます。
app/routesがfeatures/users/を利用するといったイメージです。
src/components:アプリ共通のUIを置く
特定の業務機能に依存しない、複数の画面や機能で使う部品を置きます。
src/components/
├── layouts/ # 画面共通の骨組み
└── ui/ # 汎用的なUI部品
layouts/はヘッダー・サイドバー等の共通レイアウト、
ui/はButton, Input, Dialog等の基本部品を置きます。
shadcn/uiで追加する部品等もここに置かれることが多いんじゃないでしょうか。
src/lib/:ライブラリや外部サービスの共通設定を置く
外部ライブラリ等、アプリ内で使いやすくするためのコードを置きます。
src/lib/
├── api-client.ts # 通信ライブラリの共通設定
└── react-query.ts # データ取得ライブラリの共通設定
👇実際のコードからの抜粋
export const api = Axios.create({
baseURL: env.API_URL,
});
ここではHTTP通信にAxiosを使っていて、
APIのURLの共通部分を先に設定しています。
同じファイルには、次の共通処理も書かれており、これらを各機能に書かず、一か所にまとめています。
| タイミング | 処理 |
|---|---|
| リクエスト前 | JSONを受け取りたいことを伝え、認証情報を含める設定をする |
| 成功時 | レスポンスからデータ部分を取り出す |
| 失敗時 | エラー通知を表示する |
| 未認証時 | ログイン画面へ移動する |
他にもTanStack Queryだとかが書かれています。
おわり
雑感
本当にかなり色々端折ったので適宜自己学習で補うことをお勧めします。
バックエンド側についてもそのうち書きます。