こちらの記事は金属加工プラットフォームを開発・運用するCatallaxyのCatallaxy Advent Calendar 2022の12/9の記事となります。
Reactを使っている既存システムの状態管理を整理していくうちに色々問題があったので、どういう点が問題だったのかと解消方法を考えてみました。
前提
既存システムでは、各コンポーネントで必要な情報を取得するために都度APIを呼び出すようにしています。
いわゆるReduxを用いたFluxパターンみたいなことはしていません。
状態を管理する粒度が曖昧で起きた問題
画面全体で状態が共有されてしまっている
一つの画面内に、A、B、Cのようなコンポーネントがあった場合に、既存の実装では画面全体で同じデータ構造を使っている箇所がありました。
A・B・Cで必要なデータ構造がそれぞれ違うのですが、汎用的な型定義がされていて実際にそれぞれのコンポーネントで必要なデータ構造がどうなっているのかわかりませんでした。
仮に
- A(オーバービュー)
- B(詳細)
- C(オプション)
とすると、扱いたいデータの粒度はそれぞれ異なっているのですが、実際に定義されている型はAもBもCも含むような型になっていた感じです。
このときにBで更新があってそれをトリガーにAの表示状態を更新しようとした時に、BのオブジェクトをAに渡して表示を更新しようとしている箇所がありました。
型定義としては合っているのでAのオブジェクトはBのもので上書きされるのですが、実際にはAの型定義はBの型定義で包含されない状態だったので、Aで表示されていたものが意図せず変更されてしまいました。
これは当初の設計で意図していた部分(各コンポーネントで必要なデータをAPIから呼び出して取得する)が、修正を続けるうちに曖昧になってしまったために起きたと考えられます。
問題を起こさないためには、A・B・Cでそれぞれ必要な型定義をしてそれぞれのコンポーネントを疎結合にするようにしないといけないなぁと思いました。
コンポーネント間でのPropsの引き渡し
状態管理ライブラリを用いる前提の設計では起こりづらい問題なのかもしれませんが、既存システムではコンポーネントの上流から下流にPropsを引き渡すような設計をしていました。
これが、前述のA・B・Cのような大きなコンポーネント間でもPropsの引き渡しが起こってしまっていたため、結合が密になってしまっていました。
例として挙げると、Aの孫コンポーネントからAにハンドラー経由で渡されてきたものが、そのままBを経由してPropsづたいにBの孫コンポーネントまでそのまま渡されていました。
大雑把に考えるとAの孫コンポーネント→A→B→Bの孫コンポーネントというひと繋がりの結合になってしまっていて、Aの孫コンポーネントでそのPropsに修正を加えた時にすべてのつながりを追って検証しないといけなくなってしまうことになります。
このような場合、AとBの間での引き渡し時に分離をする必要があるので、Propsのオブジェクトをそのまま渡すのではなくて再定義されたもの、もしくはAPIから取得し直したものをBの孫コンポーネントに渡すようにしなければいけないかなと思いました。
状態へのアクセス方法を管理する
各コンポーネント内、システムグローバルで様々な情報を保持すると思いますが、アクセスできる情報は整理・制限が必要だと感じました。
この辺の設計をせずに「あー、ここにデータあるから使おう」とかやってしまうと、上記のような問題が発生しやすくなると思います。
また、形骸化しやすいので設計をしたらチームへの共有が必要になりますね。
ESLintとかプログラミング設計でこの辺が制限・設計できればいいのですが、仕組み的に難しい所もあると思います。
状態管理ライブラリで管理された状態へのアクセス方法
状態管理ライブラリを採用することになったときに、RecoilやJotaiのようなAtomアーキテクチャのものを選定していたのですが、お手軽に扱える一方で、使用する手段やスコープを狭めないと思わぬ変更がされると思います。
なので、Reactの場合はカスタムフックからのみ値を変更できるようにしたりしてその辺りのケアをする必要があります。
色々調べてたら似たような設計を見つけたので参考にしたいです。
まとめ
状態の管理については
- 状態を変更できるスコープ
- 状態を共有するスコープ
これらを必要最小限にして、コンポーネント間では
- 情報の受け渡しはオブジェクトのキーのみ
- シンプルなイベントトリガーを使う
のようになるべく結合度を低くしていくような設計が必要だと思いました。
状態管理のライブラリや設計パターンによってこの辺は多少変わってくるかもしれませんが、必要以上にオープンな設計にしていると思わぬデグレが発生しやすくなるのは間違い無いかなと思います。
コードレビューをするときも影響範囲が読みにくいですしお寿司。