現在はuseEffect
依存関係の 1 つだけが変更されたときに起動されます。
すべての依存関係が変更されたときに、それを更新したり起動したりするにはどうすればよいでしょうか?
ベストアンサー1
すべての依存関係が変更されたときにエフェクトを呼び出すためのロジックを追加する必要があります。これでuseEffectAllDepsChange
目的の動作が実現されるはずです。
ここでの戦略は、以前の依存関係を現在の依存関係と比較することです。すべてが異なっていない場合は、以前の依存関係を参照に保持し、すべてが異なるまで更新しません。これにより、エフェクトが呼び出される前に依存関係を複数回変更できます。
import React, { useEffect, useState, useRef } from "react";
// taken from https://usehooks.com/usePrevious/
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
function useEffectAllDepsChange(fn, deps) {
const prevDeps = usePrevious(deps);
const changeTarget = useRef();
useEffect(() => {
// nothing to compare to yet
if (changeTarget.current === undefined) {
changeTarget.current = prevDeps;
}
// we're mounting, so call the callback
if (changeTarget.current === undefined) {
return fn();
}
// make sure every dependency has changed
if (changeTarget.current.every((dep, i) => dep !== deps[i])) {
changeTarget.current = deps;
return fn();
}
}, [fn, prevDeps, deps]);
}
export default function App() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
useEffectAllDepsChange(() => {
console.log("running effect", [a, b]);
}, [a, b]);
return (
<div>
<button onClick={() => setA((prev) => prev + 1)}>A: {a}</button>
<button onClick={() => setB((prev) => prev + 1)}>B: {b}</button>
</div>
);
}
Richard にヒントを得た別のアプローチはよりクリーンですが、更新ごとにレンダリングが増えるという欠点があります。
function useEffectAllDepsChange(fn, deps) {
const [changeTarget, setChangeTarget] = useState(deps);
useEffect(() => {
setChangeTarget(prev => {
if (prev.every((dep, i) => dep !== deps[i])) {
return deps;
}
return prev;
});
}, [deps]);
useEffect(fn, changeTarget);
}