hacomono TECH BLOG

フィットネスクラブやスクールなどの顧客管理・予約・決済を行う、業界特化型SaaS「hacomono」を提供する会社のテックブログです!

巨大なコンポーネントを作らないために



こんにちは、フィーチャー部でエンジニアをしている あおさん です。

最近、フロントエンドの実装を行う機会が増えてきました。新しい画面を作るのは楽しいのですが、一つ大きな悩みがありました。それは、実装が進むにつれて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行以内に収める」という目標を実践してみたら
「あ、これってパズルみたいで楽しい!」と思えるようになり、複雑・煩雑な画面の実装に怯えることもなくなりました。

もし「ファイルが長くなりすぎて読みづらい!」と悩んでいる方がいたら、ぜひ一度、画面の要素を最小単位まで洗い出して、自分なりの「パズル」を作るところから始めてみてはいかがでしょう。



💁 関連記事