useRefフックはuseEffectの依存配列に使えない
開発をしていく中で、useRefフックをuseEffectの依存配列に組み込んで変更を検知させようとしたところうまく反応してくれず、ハマりました。
そして調べてみるとなんとuseRefの変更をuseEffectで検知することはできないとのこと...
なぜできないのか、理由を記載しておきます。
そもそもuseEffectの依存配列とは
useEffect(() => {
// 何かの処理
}, [/* 依存配列 */]);
useEffectの依存配列とはuseEffectの最後に記載する配列のことです。
useEffectフックはコンポーネントのマウント時の他に、この依存配列の値が変わった際にも実行されます。
例えば、カウンターの数値を監視し、その値が変わるたびに処理を動かすといったことができます。
この依存配列には、主にuseStateフックを渡して状態が変わるたびに動作をさせます。
useRefをuseEffectの依存配列に渡した場合
useStateフックでなく、useRefフックの値の変更を検知したいということもあるでしょう。
どちらも値を保持することのできるフックのため、useRefでも問題なく動きそうではあります。
しかし、useRefフックを依存配列に渡した場合、いくらuseRef.currentの値が変更されたとしてもuseEffectが実行されることはありません。
なぜuseEffectがuseRefの変更を検知してくれないのか
ここで気になるのは、なぜuseStateの変更は検知してくれるのに、useRefの変更は検知してくれないのかという点です。
これは非常に簡単で、useRefの参照はマウントされてからアンマウントされるまで不変だからです。
そんなバカな、useRefは値を入れ替えることもできるじゃないかと私も思っていました。
ですが、useRefを用いる際に値を変更できるのはuseRef自体ではありません。
useRef.currentオプションなのです。
つまり、useRef.currentオプションの中身をいかに書き換えようが、useRef自体の参照は変化しないため、
useEffectは値が変化していないとみなし、検知してくれないということです。
最後に
useRefの細かい仕様を知らずに使っていたために痛い目を見たという事例でした。
この後、useRefを単純にuseStateに置き換えて解決を試みたのですが、useRefが同期的なのに対し、useStateは非同期的なため、単純に置き換えることはできずトリッキーな解決法に頼らざるを得ませんでした。
この辺の仕様もまだまだ理解が進んではいないので、見つけた落とし穴は適宜まとめていこうと思います。
Discussion
失礼します。
useEffect が「積極的に依存配列の値の変更を監視している」のではなく、
あくまで「再レンダリングされたときに、依存配列の値が変わったか確認して、変わっていれば実行する」という挙動を取っているのが原因です。
なので、useEffect は、再レンダリングを伴う変化(useState, useReducer で管理されたステート または useSyncExternalStore によって同期された値)によって引き起こされる変更は検知しますが、伴わない変化は検知できません。
手前味噌ですが参考になれば
そうだったんですね…無知を晒してしまって申し訳ないです…
そもそもuseEffectが再レンダリングされない限り感知しない仕様とは…
そうなんです…
ほかにも、React には「理解してしまえばパワフルだけど、初見だと直感に反する(特に、他のフレームワークなどの感覚が邪魔しやすい)」ような所が多々あるので、公式ドキュメントに目を通すと良いかもしれません 👍