当前useEffect
仅在依赖项之一发生更改时触发。
当两个(或所有)依赖项都发生变化时,我如何更新它/使用它来回火?
当前useEffect
仅在依赖项之一发生更改时触发。
当两个(或所有)依赖项都发生变化时,我如何更新它/使用它来回火?
当所有依赖项都发生变化时,您需要添加一些逻辑来调用您的效果。这useEffectAllDepsChange
应该可以实现您想要的行为。
这里的策略是将之前的 deps 与当前的 deps 进行比较。如果它们不完全不同,我们将之前的 deps 保留在 ref 中,并且在它们不同之前不要更新它。这允许您在调用效果之前多次更改 deps。
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);
}
您必须跟踪依赖项的先前值,并检查是否只有其中一个发生了更改,或者两者/全部发生了更改。基本实现可能如下所示:
import React from "react";
const usePrev = value => {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
const App = () => {
const [foo, setFoo] = React.useState(0);
const [bar, setBar] = React.useState(0);
const prevFoo = usePrev(foo);
const prevBar = usePrev(bar);
React.useEffect(() => {
if (prevFoo !== foo && prevBar !== bar) {
console.log("both foo and bar changed!");
}
}, [prevFoo, prevBar, foo, bar]);
return (
<div className="App">
<h2>foo: {foo}</h2>
<h2>bar: {bar}</h2>
<button onClick={() => setFoo(v => v + 1)}>Increment foo</button>
<button onClick={() => setBar(v => v + 1)}>Increment bar</button>
<button
onClick={() => {
setFoo(v => v + 1);
setBar(v => v + 1);
}}
>
Increment both
</button>
</div>
);
};
export default App;
这里还有一个CodeSandbox 链接可以玩。
你可以在usePrev
别处检查钩子是如何工作的,例如这里。
我喜欢@AustinBrunkhorst 的灵魂,但你可以用更少的代码来做到这一点。
使用仅在满足条件时更新的状态对象,并将其设置在第二个 useEffect 中。
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
const [ab, setAB] = useState({a, b});
useEffect(() => {
setAB(prev => {
console.log('prev AB', prev)
return (a !== prev.a && b !== prev.b)
? {a,b}
: prev; // do nothing
})
}, [a, b])
useEffect(() => {
console.log('both have changed')
}, [ab])
return (
<div className="App">
<div>Click on a button to increment its value.</div>
<button onClick={() => setA((prev) => prev + 1)}>A: {a}</button>
<button onClick={() => setB((prev) => prev + 1)}>B: {b}</button>
</div>
);
}
为了演示如何以各种方式组合钩子,这是我的方法。这不会调用初始归因中的效果。
import React, { useEffect, useRef, useState } from "react";
import "./styles.css";
function usePrevious(state) {
const ref = useRef();
useEffect(() => {
ref.current = state;
});
return ref.current;
}
function useAllChanged(callback, array) {
const previousArray = usePrevious(array);
console.log("useAllChanged", array, previousArray);
if (previousArray === undefined) return;
const allChanged = array.every((state, index) => {
const previous = previousArray[index];
return previous !== state;
});
if (allChanged) {
callback(array, previousArray);
}
}
const randomIncrement = () => Math.floor(Math.random() * 4);
export default function App() {
const [state1, setState1] = useState(0);
const [state2, setState2] = useState(0);
const [state3, setState3] = useState(0);
useAllChanged(
(state, prev) => {
alert("Everything changed!");
console.info(state, prev);
},
[state1, state2, state3]
);
const onClick = () => {
console.info("onClick");
setState1(state => state + randomIncrement());
setState2(state => state + randomIncrement());
setState3(state => state + randomIncrement());
};
return (
<div className="App">
<p>State 1: {state1}</p>
<p>State 2: {state2}</p>
<p>State 3: {state3}</p>
<button onClick={onClick}>Randomly increment</button>
</div>
);
}