はじめに
勉強とアウトプットを兼ねて、Webアプリ開発の記録を記事にしてみます。
個人のメモ的な側面が強いです。
Web開発初心者すぎて至らぬところだらけだと思いますが、暖かい目で見ていただければ幸いです。
↓ これまでの内容 ↓
【Day0】アプリが完成するまで毎日投稿
【Day1】開発環境を考える
【Day2】開発環境を構築する、、、はずでした
【Day3】まずはフロントだけ開発環境を構築する
【Day4】バックエンドの開発環境を整えつつ、マルチコンテナ対応のDevcontainerにする
【Day5】お試し実装!ファイルをアップロードしてバックエンドで処理する
【Day6】Docker Desktopが起動しなくなり泣きながら原因調査する
【Day7】画像にフレームとExif情報を付けてみる
【Day8】フロントに表示する画像のサイズを小さくする
【Day9】フロントエンドとバックエンドをそれぞれデプロイする
【Day10】結局コンテナ1つで開発する方が楽かもしれない
【Day11】アプリが一通り完成!!
【Day12】FastAPIのコードをリファクタリングする
【Day13】機能実装!フレームに記載するExif情報を編集できるようにする(前編)
前回やったこと
前回は機能を実装しようとしましたが、ソースがあまりにもスパゲッティすぎて今のままで実装できませんでした。
スパゲッティコードは美味しくない!泣
今回やったこと
前回の反省を生かして、改めてフロント側もリファクタリングして、今後の機能追加を容易に行えるようにしました。
Reactのリファクタリング
Chat GPTに相談
リファクタリングするため、Chat GPTにReactプロジェクトのデファクトスタンダードなディレクトリ構成を教えてもらいました。
今回はこちらの内容に従って実装してみました!
✅ ディレクトリ構成(例)
project-root/
├── public/
├── src/
│ ├── assets/ # 画像・フォントなどの静的アセット
│ ├── components/ # 再利用可能な汎用UIコンポーネント
│ ├── features/ # 機能(ドメイン)ごとの機能モジュール(UI + ロジック)
│ │ └── uploader/ # 例: 画像アップロード機能
│ │ ├── components/ # アップローダー専用UIコンポーネント
│ │ ├── hooks/ # カスタムフック
│ │ ├── types.ts # 型定義
│ │ ├── utils.ts # 専用ユーティリティ関数
│ │ └── index.tsx # 機能のエントリポイント
│ ├── pages/ # ページ単位のコンポーネント(React Router 用など)
│ ├── hooks/ # アプリ全体で使う汎用カスタムフック
│ ├── utils/ # 共通のユーティリティ関数
│ ├── types/ # 全体で使う型定義(APIレスポンス型など)
│ ├── constants/ # 定数群(API URLなど)
│ ├── api/ # API通信系(axios定義、APIクライアントなど)
│ ├── App.tsx
│ └── main.tsx
├── .env
├── tsconfig.json
└── package.json
✨ コンポーネント構成・分割の考え方
| 種別 | 内容 |
| ------------- | --------------------------------------------- |
| `components/` | **再利用可能なUI部品**(Button, Modal, Card, etc.) |
| `features/` | **機能単位でUI + ロジックを一緒に管理**(例: アップローダーなど) |
| `pages/` | ルーティングに対応した**画面単位のコンポーネント**(Next.jsなら `app/`) |
| `hooks/` | カスタムフック(アプリ共通で再利用される処理) |
| `utils/` | 日付変換・バリデーションなどの純粋関数 |
🔁 再利用性の考え方
| 場所 | 再利用前提 | 具体例 |
| ------------- | ----- | ------------------------------------------ |
| `components/` | 高 | Button, InputField, Modal, ToggleSwitch など |
| `features/` | 中〜低 | アプリ内で再利用される程度(Uploaderなど) |
| `pages/` | 低 | アプリ内で1回使われる(トップ画面など) |
リファクタリング前
ディレクトリ構成
ImageUploader.tsx
というコンポーネントにすべての機能を詰め込んでしまい、神コンポーネントになってました。
.
├── .devcontainer/
│ └── (省略)
├── Dockerfile
├── backend
│ └── (省略)
└── frontend
├── node_modules/
├── src/
│ ├── assets
│ │ └── react.svg
│ ├── components
│ │ ├── ImageUploader.css
│ │ ├── ImageUploader.tsx ☆ ほとんどの実装が入ってる
│ │ ├── Title.css
│ │ ├── Title.tsx
│ │ ├── ToggleSwitch.css
│ │ └── ToggleSwitch.tsx
│ ├── types
│ │ └── piexifjs.d.ts
│ ├── utils
│ │ ├── extractExif.ts
│ │ └── imageUtils.ts
│ ├── App.css
│ ├── App.tsx
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
コンポーネント構成
なんと!コンポーネントは2つしか作ってませんでした!!!!
厳密にはトグルスイッチ用にもう1個あるけど、あって無いようなもの。
Reactらしさを全然活かせてないですね。。。
リファクタリング後
ディレクトリ構成
features/imageUploader
以下に今回の機能をまとめました。
コンポーネントも機能に沿って分割しました。
.
├── .devcontainer/
│ └── (省略)
├── Dockerfile
├── backend
│ └── (省略)
└── frontend
├── node_modules/
├── src/
│ ├── assets
│ │ └── react.svg
│ ├── components
│ │ ├── Viewer.css
│ │ ├── Viewer.tsx
│ │ ├── Title.css
│ │ ├── Title.tsx
│ │ ├── ToggleSwitch.css
│ │ └── ToggleSwitch.tsx
│ ├── types
│ │ └── piexifjs.d.ts
│ │
│ ├── features/imageUploader
│ │ ├── components
│ │ │ ├── FileSelectForm.css
│ │ │ ├── FileSelectForm.tsx
│ │ │ ├── ImageUploader.css
│ │ │ ├── ImageUploader.tsx
│ │ │ ├── ShowExifIInfo.css
│ │ │ ├── ShowExifIInfo.tsx
│ │ │ ├── UploadButton.css
│ │ │ └── UploadButton.tsx
│ │ │
│ │ ├── types.ts
│ │ └── imageUtils.ts
│ ├── App.css
│ ├── App.tsx
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
コンポーネント構成
こんな感じのコンポーネント構成にしました!
UIもこれから拡張しやすいように、このタイミングで修正しました。
ちなみに、前回実装する予定だったExif情報をフレーム化する前に手動修正できる部分もフロント側だけは実装しました。
今後はアップロード時のオプションなども増やしていきたいなと思っているので、この構成なら対応しやすいと思います!
本日のコミット
めちゃばかでかコミットになってしまった。。。
おわりに
今回は気合を入れてReactソースのリファクタリングとコンポーネント構成およびUIの修正を行いました。
正直、一回で全部やりきるべきではなかったですね。
とはいえ、コンポーネント構成とUIは切り離せない存在だと思うので、仕方なし。
明日からはすっきりしたソースで機能実装頑張ります!