dispatchとselectorをカスタムフックにまとめて困ったこと
はじめに
「dispatchとselectorをカスタムフックにまとめたら便利じゃね?」と思ったものの、
実装してみると色々不都合が出てきた。
どうすれば正解だったのか、まだ自分の中で答えが出ていないのでメモを残しておく
dispatch のタイミングを操作できない
カスタムフックの中身は、
- useEffectでdispatch
- selectorで取得した値を返す
という流れになった。
この場合、利用するコンポーネントのトップレベルでカスタムフックを実行 → 返り値を取得 が
一連の流れとなるため、dispatchのタイミングを自由に操作できない。
e.target.valueを元にパラメータを組み立ててdispatchしたい場合
この場合は、利用するコンポーネント内にuseStateでパラメータを持ち、引数でカスタムフックに渡すことで解決した。
- イベント発生 → useState更新 → そのstateに依存するカスタムフックも発火 → store更新
という流れになったので、割と素直なフローで収まったと思う
イベントが発生したタイミングで初めてdispatchしたい場合
この場合は非常に困った。
カスタムフックをコンポーネントのトップレベルに置く都合上、スマートな解決方法が思い浮かばなかった。
引数にオブジェクトを渡すことでuseEffectの無限ループが発生する
APIとの通信時のパラメータ(オブジェクト)を引数として渡したかったが、その場合は等価性を判定できないため、毎回useEffect内の処理が発火する。その結果、無限ループが引き起こされていた。
解決方法
1. useMemo
引数を渡す前にメモ化しておけば、オブジェクトの同一性が担保されるため、カスタムフックも再発火しない。
しかし、毎回メモ化して渡すのは煩雑+レビュー等でその件を見落とす可能性大と考えると、別の方法で解決したい
2. カスタムフック内で等価性を判定
- 前回の引数をカスタムフック 内のuseStateで保持
- Ramda.jsのequals()で前回の引数と今回の引数を比較
- 差分が無ければ、useEffect内で早期リターン
この流れでなんとかなった。
カスタムフック内の複雑性は増したものの、インターフェースはシンプルに保てたのでまあ良し。
画面によってselectorで取得する値を出し分けたい
シンプルにbooleanを引数で渡し、それによって返す値を分岐させることで解決
まとめ(的なメモ)
いくつかの問題は解決できたものの、「イベントが発生したタイミングで初めてdispatchしたい」問題は依然として残っている。
それを踏まえると、dispatchとselectorをカスタムフック にまとめることは難しいかも。
また、柔軟性を担保しようとした結果、カスタムフックの内部がどんどん複雑化していく傾向もあった。
現状だとまだ問題ないレベルだが、今後の運用、改修によってはスパゲッティコードに繋がるかもしれない。
結局のところ、dispatchやselectorといった単位で細かく分かれているからこそ、ソースコードのシンプルさを保つことができている感がある。
それに、「fetchして値を返す」というメンタルモデル自体がfluxに上手くハマっていない。
「Action発行にのみ関心を持つ関数」、「値の参照にのみ関心を持つ関数」というように切り分けることを意識すべきかも(自省)。