フロントエンド開発における共通化は最低限に留めるべき

2021-05-26

よくフロントエンドディベロッパーの方から「コンポーネントの共通化ってどうすれば良いですか?」とか「処理の共通化はどう行うべきですか?」という質問をいただきます。

対して、個人的にはフロントエンド開発において共通化はあまり行わないほうがスムーズに進むことが多い、と思っています。


フロントエンド開発において共通化というと、大きく 2 種類に分けられると思っています。

  1. コンポーネントの共通化
  2. コンポーネント以外の処理の共通化

で、個人的には 1, 2 ともにあまり共通化すべきではないと思っているので、その根拠を書いていこうと思います。


コンポーネントの共通化はあまり意識しなくて良い

前提の話になるのですが、コンポーネント設計を行う際、コンポーネントの共通化って意識する必要がないと思っています。

つまり「コンポーネントの共通化はどうすれば良いのか?」という質問に対する自分の答えとしては、「共通化を前提としたコンポーネント設計を意識するのはやめたほうが良いと思います」という感じになります。

そもそもコンポーネント設計とはなんなのか?というところから考え直してほしいのですが。

10 年くらい前のフロントエンド開発では、1 画面に対し 1 枚の HTML ファイル(= 1 page コンポーネント)が切られていました。

ところが、少しずつ Web サービスに求められる機能が大きくなってきたので、コンポーネントを然るべき粒度で切り出すことによって、保守性を高める手法が主流になったわけです。

ただ、各画面で共通の Header や Footer などはコンポーネントとして切り出しやすいため、コンポーネントの粒度よりも先に共通化を考えてしまいがちなのかなーと思うのですが。

コンポーネントは最適な粒度で切り出すことが大切なわけで、切り出したコンポーネントがたまたま複数の画面で使いこなせるようになっていた、くらいに考えても差し支えないと思います。

そのため、共通化できないコンポーネントは切り出してはいけないのかというと、まったくそんなことはないですし。

逆に共通化できそうなパーツがあったとしても、コンポーネントを切り出す粒度に沿っていなければ、それはコンポーネントとして切り出すべきではないと思っています。

Atomic Design であれば Atomic Design に沿ってコンポーネントを切り出すべきですし、Atomic Design を採択していなければ、そのプロジェクト内のコンポーネントの設計ルールに沿って切り出すことが大切です。

処理の共通化は慎重に行うこと

React であれば、よくカスタム Hooks として共通の処理を切り出す現場を目にしますが、個人的には処理の共通化も最低限にすべきだと思っています。

たとえば、1 つのコンポーネントでしか使用されない処理をカスタム Hooks として切り出す必要はないと思っています。

またどうしても切り出したいのであれば、切り出すのは良いけれども、それを共通の処理として扱う必要はないよね?と思っています。

たとえば以下のようなプロジェクトの場合。

.
├── containers
│     └── Hoge
│        └── index.tsx
└── hooks

Hoge コンポーネント内でのみ使用されている処理は Hoge コンポーネントの内部に定義すれば十分かなと思っています。

とはいえ、Hoge コンポーネント内の処理が複雑で長くなってしまったので、外部ファイルに切り出したい!となった場合も、以下のように切り出せば良いと思っています。

.
├── containers
│     └── Hoge
│        ├── index.tsx
│        └── hooks
│           └── useFuga
│              └── index.ts
└── hooks

ところが、なぜか以下のように切りたがる人が多いんですよね、自分にはこれが不思議で仕方ないのですが。

├── containers
│     └── Hoge
│        └── index.tsx
└── hooks
    └── useFuga
       └── index.ts

トップディレクトリへ近いところに切られた Hooks は、いわゆるグローバル変数となってしまいます。

で、当たり前ですが、グローバル変数は少ないに越したことはないわけです。

またグローバルな各カスタム Hooks 内の処理は、可能な限りシンプルに留めるべきです。

グローバルなカスタム Hooks などの場合、コンポーネントのケースとは異なり処理の共通化を前提とした設計になります。

またコンポーネントのケースと異なり、各機能の粒度に対する厳密なルールを定めることが難しいため、可能な限りシンプルな処理に留めなければ、すぐに設計が破綻します。

そのため、場合によっては複数のコンポーネント間において同じ処理が実行されるようなケースであったとしても、あえて共通化せず、同じ処理を各々に書いたほうが最適なケースも存在すると思っています。

共通化に失敗している具体的な例を上げると、作成処理と編集処理を無理やり 1 つにまとめようとしてうまくいかないケースなどはよく目にします。

冷静になって考えてみると、作成処理と編集処理で共通化が可能なのは、せいぜいバリデーションチェックと Submit 前の処理程度の過ぎません。

初期値はもちろん異なりますし、Submit  によって叩かれる Api は確実に異なります。

また Filed についても、作成と編集で叩く Api が異なる以上は【たまたま】一致しているに過ぎないケースも多いわけで、全体を見ると共通化しないほうが良いケースのほうが多いです。


両ケースに共通して言えることですが、フロントエンド開発における下手な共通化は、プロジェクトの破綻や保守性の低下に非常に大きく影響してきます。

とくにフロントエンド開発に慣れていないうちは、共通化なんて考える必要はないと思っています。

同じコンポーネントや同じ処理であったとしても、愚直に書くほうが結果的に良いケースのほうが多いと思います。

共通化を最低限に留めることによって修正が容易になり、1 つの修正が多岐にわたることもなくなりますし、気づかないうちに思いも寄らないところでバグが発生していた…みたいなことも防げます。

最近は便利なパッケージも増えているので、共通処理は外部パッケージにまかせて、自分たちは処理のカプセル化を意識して開発を行うことが、スムーズな開発につながるのかなーと思います。