Open5

dispatchとselectorをカスタムフックにまとめて困ったこと

KAMYKAMY

はじめに

「dispatchとselectorをカスタムフックにまとめたら便利じゃね?」と思ったものの、
実装してみると色々不都合が出てきた。
どうすれば正解だったのか、まだ自分の中で答えが出ていないのでメモを残しておく

KAMYKAMY

dispatch のタイミングを操作できない

カスタムフックの中身は、

  • useEffectでdispatch
  • selectorで取得した値を返す

という流れになった。
この場合、利用するコンポーネントのトップレベルでカスタムフックを実行 → 返り値を取得 が
一連の流れとなるため、dispatchのタイミングを自由に操作できない。

e.target.valueを元にパラメータを組み立ててdispatchしたい場合

この場合は、利用するコンポーネント内にuseStateでパラメータを持ち、引数でカスタムフックに渡すことで解決した。

  • イベント発生 → useState更新 → そのstateに依存するカスタムフックも発火 → store更新

という流れになったので、割と素直なフローで収まったと思う

イベントが発生したタイミングで初めてdispatchしたい場合

この場合は非常に困った。
カスタムフックをコンポーネントのトップレベルに置く都合上、スマートな解決方法が思い浮かばなかった。

KAMYKAMY

引数にオブジェクトを渡すことでuseEffectの無限ループが発生する

APIとの通信時のパラメータ(オブジェクト)を引数として渡したかったが、その場合は等価性を判定できないため、毎回useEffect内の処理が発火する。その結果、無限ループが引き起こされていた。

解決方法

1. useMemo

引数を渡す前にメモ化しておけば、オブジェクトの同一性が担保されるため、カスタムフックも再発火しない。
しかし、毎回メモ化して渡すのは煩雑+レビュー等でその件を見落とす可能性大と考えると、別の方法で解決したい

2. カスタムフック内で等価性を判定

  • 前回の引数をカスタムフック 内のuseStateで保持
  • Ramda.jsのequals()で前回の引数と今回の引数を比較
  • 差分が無ければ、useEffect内で早期リターン

この流れでなんとかなった。
カスタムフック内の複雑性は増したものの、インターフェースはシンプルに保てたのでまあ良し。

KAMYKAMY

画面によってselectorで取得する値を出し分けたい

シンプルにbooleanを引数で渡し、それによって返す値を分岐させることで解決

KAMYKAMY

まとめ(的なメモ)

いくつかの問題は解決できたものの、「イベントが発生したタイミングで初めてdispatchしたい」問題は依然として残っている。
それを踏まえると、dispatchとselectorをカスタムフック にまとめることは難しいかも。

また、柔軟性を担保しようとした結果、カスタムフックの内部がどんどん複雑化していく傾向もあった。
現状だとまだ問題ないレベルだが、今後の運用、改修によってはスパゲッティコードに繋がるかもしれない。

結局のところ、dispatchやselectorといった単位で細かく分かれているからこそ、ソースコードのシンプルさを保つことができている感がある。
それに、「fetchして値を返す」というメンタルモデル自体がfluxに上手くハマっていない。
「Action発行にのみ関心を持つ関数」、「値の参照にのみ関心を持つ関数」というように切り分けることを意識すべきかも(自省)。