Better to write bad code

by 37108 at 2024/06/11

DRY原則や関心の分離を意識して、不必要な繰り返しを避けることは美しいReactのコードを書く上で重要です。しかし、コンポーネントを作成する際、必ずしも短いコードが最適とは限りません。 コンポーネントは見た目(UI)と振る舞い(ロジック)で構成され、見た目が似ていても振る舞いが異なるケースは多々あります。

例えば、見た目は同じボタンでも、片方は<button>要素として機能し、もう片方は<a>要素としてリンクとして振る舞う場合があります。また、コンポーネント内のロジックをCustom Hookに切り出す、または再利用性を高めるために小さなコンポーネントに分割するといった工夫も必要になることがあります。

本稿では、私の経験に基づき、実用的なReactコンポーネントの検討方法について解説します。あくまで経験則ですので、全てのケースに当てはまるとは限りませんが、1つの参考としていただければ幸いです。

大きなコンポーネントから始める

ロジックとJSXを含む大きなコンポーネントを作成します。チームのルール(例:API通信はCustom Hookで記述)があればそれに従い、可能な限り包括的なコンポーネントを目指しましょう。 そもそも要件を見ながらどこを分割するかなどを細かく検討して書くより、PoCのように要件を満たすコードを書くのが1番簡単でかつ、大きなコンポーネントを分割するリファクタリングは、小さなコンポーネントを統合するよりも容易だからそこを目指します。

最初から完璧な設計は難しいです。リファクタリングを前提に、将来の変化に対応しやすい大きなコンポーネントから始めることが重要です。 リファクタリングをしやすいコードを書くのが理想ではありますが、その判断ができない状態であれば大きな状態を保つことを考えましょう。

困ったら分割を検討する

経験則によらない判断基準を載せるのであれば、問題が起きたかどうかを焦点にします。 1つの大きなコンポーネントで問題が発生した場合にのみ、分割を検討します。早すぎる分割は、コードの複雑化や抽象化の失敗に繋がる可能性があります。

見た目と抽象化した振る舞いの整合性を確認する

Figmaで複数の箇所で利用されている似たコンポーネントがあり、それらの振る舞いが異なる場合を考えます。 この場合は抽象化された振る舞い(propsの型)が全てのケースに合致するかを検討します。

コンポーネントの中にボタンが1つで、そのボタン押下時に発火される関数が常に VoidFunction である場合はそれは共通化できるコンポーネントになります(handleClick をpropとして受け入れる見た目が同じコンポーネントを作れば良いですね)。型レベルで共通化が難しい場合は、見た目が似ていても別のコンポーネントとして作成する方が良いでしょう。

開発者の目線で分割を考える

コンポーネントは見た目、振る舞い、状態から成り立ちます。それぞれの要素は異なるテスト手法で検証されます。見た目は目視、VRT、アクセシビリティテストなどで、振る舞いは別途テストで確認します。 重要なのは、多くのテストケースは見た目か振る舞いのどちらか一方に焦点を当てていることです。特に振る舞いのテストでは、Reactコンポーネントのレンダリング結果(見た目)は重要ではありません。

そこで、振る舞いが複雑な場合は、Custom Hookに切り出すことで、テストを分離しやすくなります。これにより、レンダリング不要な振る舞いのテストが効率化され、テスト全体のメンテナンス性も向上します。

補足ですが、API呼び出しや簡単な状態更新程度のロジックであれば、私は無理に分割しません。MSW(Mock Service Worker)などを活用してHTTPリクエストをモックし、コンポーネント全体のテストを行います。 なぜなら、小さなロジックのために細かくテストを分割すると、管理コストが増大する可能性があるからです。問題がなければ、あえて分割せず、大きなコンポーネントとして扱う方がシンプルで良い場合もあります。

近くにファイルを配置する

あるコンポーネントのみで利用するCustom Hooks や内部的なコンポーネントを作成する場合はそのコンポーネントと同じディレクトリ上に作成しましょう。 例えば、 Population というコンポーネントがあるとしたら、その本体は population/index.tsx に、hooksは population/useX.ts に配置するような形です。 あくまでも共通部品ではなく、そのコンポーネントにスコープが絞られたものであることが分かりやすく、リファクタリング対象も同時に把握しやすくなります。

まとめ

最初から完璧なコードを書くことは不可能です。開発が継続する限り、常に改善の余地はあります。したがって、現時点でのベストなコードを書きつつ、将来の変更に柔軟に対応できる構造を目指しましょう。 最善のコードを目指し続けるのは理想ではありますが、判断がつかない状態で闇雲に進めた結果として大きな負債になることもあります。

なので、大きなコンポーネントから始め、共通化の可能性を検討していくことが有効です。判断に迷う場合は、分割を先延ばしにすることも選択肢の1つです。 将来の良い状態を保つために今はあえて悪いと思える状態を放置するというのも手法であることを頭の片隅に入れてみてください。