フロントエンドのコンポーネント設計、なぜすぐカオスになるのか?〜サーバーサイドエンジニアが考えてみた〜
思考の雑記事ですが、今後技術系も思考をまとめていこうと思います。
雑記事なので雑です笑
「ちょっとこのUI変更しよう」「あれ、このコンポーネント何してるんだっけ?」
気づけば謎の state と props が増え、コンポーネントは肥大化し、状態管理が絡まり、リファクタリングする気力も削がれる。。。
一方で、サーバーサイドのクラス設計はそこまでグチャグチャになりづらい気がするんですよね。
自分の経験上でもフロントエンドがカオスになってるPJが今まで散見していたので自分なりに、 なぜフロントエンドはカオスになりがちなのか? について言語化してみました。
(前提として自分の経験が多いReactでのSPAのフロントエンドを対象に書いています。)
1. UI とロジックが密結合しやすい
フロントエンドのコンポーネントって、基本的に 「見た目」 と 「振る舞い」 がセットになっています。
React だと JSX の中にロジックが書かれて、ちょっとした処理ならつい useEffect を追加しちゃったりしますよね。
useEffect(() => {
fetchData().then(setData);
}, []);
最初は「ちょっとデータを取ってきて表示するだけ」のつもりだったのに、
気づけば useEffect に副作用が増え、UI の状態管理と絡まっていきます。
サーバーサイドなら「データ取得はRepository」「ビジネスロジックはService」とかで分離するのが普通ですが、フロントエンドだとつい「とりあえずこのコンポーネントでやっちゃえ」となりがち。
そして、コンポーネントの責務がどんどん増えていくわけです。
2. コンポーネントの粒度がブレやすい
フロントエンドのコンポーネントって、どこまで分けるのが正解か難しいですよね。
たとえば、こんな UI を作るときに細かくコンポーネントを考えると:
<Card>
<Title>タイトル</Title>
<Description>説明文</Description>
<Button>クリック</Button>
</Card>
• Card の中で Title や Button を持つべきか?
• そもそも Card は props.children だけにして、カスタムしやすくするべきか?
• でも、そんなことしても Card を再利用する場面あるの?
みたいな感じで、コンポーネントの設計の意思決定が人によってバラバラになりがちです。
気づいたら「あるページでは Card の中でボタンまで含まれているのに、別のページでは Card は枠だけ」みたいな統一感のない設計になったりするんじゃないかなと思います。
3. 状態管理の影響範囲が広がりやすい
フロントエンドの設計を難しくする最大の要因、それが 「状態管理」だと断言できます。
httpはステートレスです。それに比べてフロントエンドはステートフル。この違いがフロントエンドを一層難しいものにしていると感じます。。。
状態をどこで持つかをミスると、一瞬で地獄が始まります。
• ローカル stateで済むと思ったら、親コンポーネントから props でバケツリレーすることに
• コンテキストを導入したら、どのコンポーネントが何の state を見てるのか分からなくなり
• グローバル stateに投げたら、今度はどこからでも変更できるせいで影響範囲が見えなくなり
サーバーサイドのクラス設計なら、状態は基本的に「DB or メモリ内のオブジェクト」に閉じていることが多いので「どこで変更されるのか」が明確になりやすいですよね。
でもフロントエンドでは状態が「コンポーネントのツリー構造」に依存しているので、設計をミスると修正が大変になります。
4. レイアウトとロジックが混ざりやすい
例えばコンポーネントを作っているとき、こんなコードを書くことってありますよね。
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
return (
<div>
<h2>{user?.name}</h2>
<p>{user?.bio}</p>
</div>
);
}
これは一見シンプルなのですが、「データ取得」と「表示(レンダリング)」が同じコンポーネントに詰め込まれています。
結果、API の変更が入ったときに UserProfile まで修正が必要になり、テストもしづらくなります。
サーバーサイドなら、データ取得と表示のロジックを明確に分けるのが普通ですが、フロントエンドでは「UI にちょっとしたロジックを追加したい」誘惑に抗いきれず、混ぜてしまうケースが多いと思います。
5. 設計を省略しがち
サーバーサイドの開発では、
• 「この API はどう設計する?」
• 「どのクラスにどの責務を持たせる?」
といった設計の話を最初に考えることが多いですが、フロントエンドでは「とりあえず動くものを作る」ことが優先されがちかなと思っています。
結果として、「その場しのぎで作ったコンポーネントが量産され、後から統一感がなくなる」
という状況に陥りやすいと思います。
サーバーサイドだと当たり前に用いられるSOLID原則をはじめとするDDD、クリーンアーキテクチャといった概念。アーキテクチャとしてはレイヤード、オニオン等。
このような確立された手法がまだまだ少ないのがフロントエンドの難しさかなーと思います。
フロントエンドの中でも有名だと思われるAtomic Design、Co-locationなどがありますが、DOMやCSSの設計まで考えるとサーバーサイドエンジニアとしてはもうわけわからんこっちゃ!となります。
まとめ
フロントエンドのコンポーネント設計が崩れやすい理由を整理すると、こんな感じだと思っています。
- UI とロジックが密結合しやすい
- コンポーネントの粒度がブレやすい
- 状態管理の影響範囲が広がりやすい
- レイアウトとロジックが混ざりやすい
- 設計を省略しがち
フロントエンドは、設計を意識しないとすぐにカオスになります。
逆にここら辺の特徴を捉えて設計をしっかり意識すれば、綺麗でメンテしやすいコードを書くこともできるはず。
みなさんのプロジェクトではフロントエンドのコンポーネント設計うまくいっていますか?
「こうしたら改善した!」みたいな体験があれば、ぜひ教えてください!
Discussion