こんにちは、フィーチャー部でエンジニアをしている あおさん です。
最近、フロントエンドの実装を行う機会が増えてきました。新しい画面を作るのは楽しいのですが、一つ大きな悩みがありました。それは、実装が進むにつれて1つのコンポーネントファイルがどんどん肥大化していくことです。
気づけば500行、600行……。script / template / styleタグが肥大化し、「さっき直したコードはどこだっけ?」とスクロールを繰り返す時間は、お世辞にも効率的とは言えませんでした。
そんな時、テックリードのみゅーとんさんに設計の相談をしたところ、コンポーネント設計についてのアドバイスをいただきました。その教えをもとに自分なりに試行錯誤してみると、「これって、パズルを組み立てる感覚に近いかも!」という発見があったので、その内容を共有したいと思います。
3行まとめ
- 1つのファイルに詰め込もうとすると、コードが肥大化して「どこを直せばいいか」を探すだけでもストレス。
- 理想は 1 コンポーネント 100 行以内。 実装前に「最小パーツ」を洗い出す。
- 複雑なロジックはバックエンドに寄せることで、フロントエンドは描画に専念させる。
対象読者
以下の読者を想定しています。
- Vue や React などのフロントエンド開発の経験がある方
- 1コンポーネントに全ての要素を記述してコンポーネントが肥大化した経験がある方
1. 目標は「1 コンポーネント 100 行以内」
コンポーネントを作成していく上で、一つの指標として出てきたのが「1つのコンポーネントは、script / template / styleタグをすべて含めても100行以内に収めるのが理想」という方針でした。hacomono ではフロントエンドに Vue を使用しているため、script / template / styleタグをすべて含めても100行以内としましたが、Reactであれば、Component の関数全体が 100 行以内と捉えていただけると良いかと思います。
最初は「100行なんて無理じゃないかな?」と思いましたが、いざ意識してみると、その効果は非常に大きいと感じました。
- 可読性が上がる: パッと見ただけで何をしているファイルか把握できる
- メンテナンスが楽: 直したい場所がすぐに見つかり、修正の影響範囲も小さくなる
この「100行」に収めるために必要だったのが、画面の要素を細かく「洗い出す」作業でした。
2. 「画面全体」ではなく「最小のパーツ」を洗い出す
これまでの私は、いきなり「画面全体」を完成させようとして、ルートコンポーネントに上から順番にコードを書き連ねていました。しかし、100行に収めるためには、まず「これ以上分けられない最小単位のパーツ」を定義する必要があります。
たとえば、以下のスケジュール画面。

いきなりページの全体を作るのではなく、画面の要素を以下のように細かく分けていきます。
- 予約カテゴリ:店舗が持つ予約カテゴリに関するパーツ
- 表示期間: 表示するスケジュールの期間に関するパーツ
- レッスンリスト:期間内に存在するレッスンに関するパーツ
- レッスンカード:リスト内の1レッスンに関するパーツ
- 予約状況: 「2/9人」などのレッスン毎の予約状況に関するパーツ
- レッスン日時: 「1/25(日) 18:00 - 19:00」といったレッスンが実施される日時に関するパーツ
- トライアルアイコン: 「トライアル」というラベルを出すパーツ
- etc…
実際にはまだまだパーツとしては他にもありますが、ごちゃごちゃになるのでいくつかピックアップしてみました。
こうして「ただデータを表示するだけのパーツ」を先に定義しておきます。
すると、あとのメイン作業は「あらかじめ用意したピースを、画面という枠にはめ込んでいくだけ」になります。
レッスンリストはレッスンカードをループの中にコンポーネントを置くだけですし、レッスンカードはレッスン日時や予約状況などのコンポーネントを内部に記述するだけ。日時や予約状況など、表示に必要な情報は props で渡してあげる。とてもシンプルになった気がしませんか。
この「先にピースを揃えてから組み立てる」工程が、私にはまるでパズルのように感じられ、設計が非常にやりやすくなった感覚でした。
3. 「はまる形(型)」を決めると、APIの設計についての相談が驚くほどラクになる
パーツを分けていくと、TypeScriptを使って「この部品には、どんなデータが必要か」という形(型)を定義することになります。
例えば、
// 予約状況を表示するパーツに必要なデータの「形」 interface Props { reservedCount: number // 現在の予約数 maxReservableCount: number // 最大予約可能数 }
ここで大切なのが、「フロントエンドで計算しなくていい状態」をバックエンドと一緒に作ることです。
極端な例ですが、対象のレッスンの予約情報をレスポンスとして全て受け取り、それをフロントエンド側でカウントする、などは非常に無駄なロジックとなります。
バックエンド側で計算した結果をレスポンスとして受け取り、フロントエンドはあくまでそれを描画するだけとすることで、バックエンドの実装者がもし違う場合には「バックエンド側でこの形に整えて返してほしい」と具体的に言えるようになります。
それにより、フロントエンドのコードは非常にシンプルになり、ファイルの100行制限も割とクリアできるようになりました。
4. ロジックも適切に切り出す
見た目のパーツを分けるのと同じで、中身の「計算」や「動き」も適切な粒度に分けることが大切です。
たとえば、画面固有の複雑な状態管理などは、コンポーネントの中に書かずに Composable(関数) として切り出してあげます。(React であれば Custom Hooks)
重要なのは「コンポーネントを表示に専念させる」ことです。
- 表示に集中できる: 複雑な計算式がなくなれば、見た目の修正が圧倒的にしやすくなる。
- テストが楽になる: ロジックが独立していれば、単体テストが書きやすくなる。
コンポーネントの中に if 文や計算が溢れそうになったら、それは Composable に切り出せる合図です。これについては、以下の記事が非常に参考になります。
まとめ
設計は「要素を分ける」ことから始まる
「とりあえず全部書いてから、あとで綺麗にしよう」……と思っても、一度絡まった糸を解くのは本当に大変です。
「最小単位で考える」という設計、そして「100行以内に収める」という目標を実践してみたら
「あ、これってパズルみたいで楽しい!」と思えるようになり、複雑・煩雑な画面の実装に怯えることもなくなりました。
もし「ファイルが長くなりすぎて読みづらい!」と悩んでいる方がいたら、ぜひ一度、画面の要素を最小単位まで洗い出して、自分なりの「パズル」を作るところから始めてみてはいかがでしょう。
💁 関連記事