当所有依赖项都发生变化时使用效果?
Posted
技术标签:
【中文标题】当所有依赖项都发生变化时使用效果?【英文标题】:useEffect when all dependencies have changed? 【发布时间】:2020-11-08 11:37:45 【问题描述】:目前useEffect
仅在其中一个依赖项发生更改时被触发。
当两个(或所有)依赖项都发生变化时,我如何更新它/使用它来回火?
【问题讨论】:
你能详细说明你所说的“开火”是什么意思吗?useCallback
只是一个记忆回调函数。
对不起,我把它和 useEffect 混在一起了,每次参数改变时都会触发? Firing 是指调用您在第一个参数中提供的回调。
我认为通过useEffect
API 是不可能的。
这是一个非常简单的问题,但我可以解决它。目前我使用 redux 来设置一个选项。这会触发刷新,然后使用新选项触发数据源的刷新。这将导致 useEffect 触发,因为两个选项都已更改并且新数据已更改。您可能认为只是在新数据上调用它,但是数据可以从其他来源更改,并且 useEffect 很昂贵,所以我只想在选项和 THEN 数据更改后才这样做。在数据不起作用之前触发它。也许我遗漏了一些明显的东西?
你有 Redux 吗?那么这可能是一个错误的问题,尝试在商店中解决它。
【参考方案1】:
当所有依赖项都发生变化时,您需要添加一些逻辑来调用您的效果。这是useEffectAllDepsChange
,应该可以实现您想要的行为。
这里的策略是将之前的部门与当前的部门进行比较。如果它们不完全不同,我们将之前的 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);
【讨论】:
我喜欢这个,因为它对于给定的所有依赖项都是动态的。 我喜欢它,对我的回答进行了很好的改进。更多渲染?不确定。 第二种方法更简洁。【参考方案2】:我喜欢@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>
);
【讨论】:
不错的方法。我认为这里的缺点是setAB
引起的额外渲染。我们还可以通过将此功能移至与useEffect
具有相同签名的另一个挂钩来提高可重用性。【参考方案3】:
为了演示如何以各种方式编写钩子,这是我的方法。这不会在初始属性中调用效果。
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>
);
【讨论】:
【参考方案4】:您必须跟踪依赖项的先前值,并检查是否只有其中一个发生了变化,或者两者/全部发生了变化。基本实现可能如下所示:
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 link可以玩。
您可以检查usePrev
挂钩在其他地方的工作方式,例如here。
【讨论】:
prevFoo
会在您单击“增量栏”时更新?
不,点击“增量栏”时不会更新
添加<h2>prevFoo: prevFoo</h2>
你会看到它发生。
已添加,如果我只点击“增量栏”prevFoo
保持不变。当您单击“递增 foo”或“同时递增”,然后单击“递增栏”时,它会发生变化,但这就是它的目的,它会延迟更改。
@Maxim 看起来您忘记重新加载页面 - prevFoo 开始 undefined
然后在“增量栏”时获取 0
。然后,如果您单击“增加 foo”,您不会看到“foo 和 bar 都已更改”,这是其他解决方案的行为。以上是关于当所有依赖项都发生变化时使用效果?的主要内容,如果未能解决你的问题,请参考以下文章