传递数组到 useEffect 依赖列表

每5秒钟就有一些来自长轮询的数据,我希望我的组件在每次数组的一个项目(或数组长度本身)改变时都发送一个操作。 当将数组作为依赖项传递给 useEffect 时,如何防止 useEffect 进入无限循环,但是如果值发生变化,仍然可以设法分派一些操作?

useEffect(() => {
console.log(outcomes)
}, [outcomes])

其中 outcomes是一个 ID 数组,如 [123, 234, 3212]。数组中的项可能被替换或删除,因此数组的总长度可能(但不一定)保持不变,因此不需要将 outcomes.length作为依赖项传递。

outcomes来自于 reselect 的自定义选择器:

const getOutcomes = createSelector(
someData,
data => data.map(({ outcomeId }) => outcomeId)
)
65693 次浏览

可以将 JSON.stringify(outcomes)作为依赖项列表传递:

阅读更多 给你

useEffect(() => {
console.log(outcomes)
}, [JSON.stringify(outcomes)])

另一个 ES6选项是使用 模板文字将其变为字符串。类似于 JSON.stringify(),只是结果不会包装在 []

useEffect(() => {
console.log(outcomes)
}, [`${outcomes}`])

另一个选择,如果数组大小不变,将是 传播它在:

useEffect(() => {
console.log(outcomes)
}, [ ...outcomes ])

使用 JSON.stringify()或任何深度比较方法可能效率低下,如果您提前知道对象的形状,您可以编写自己的效果挂钩,根据您自定义的等式函数的结果触发回调。

useEffect的工作原理是检查依赖数组中的每个值是否与前一个呈现中的值相同,如果其中一个值不相同,则执行回调。因此,我们只需要保留使用 useRef的感兴趣数据的实例,并且只有在自定义相等检查返回 false以触发效果时才分配一个新的实例。

function arrayEqual(a1: any[], a2: any[]) {
if (a1.length !== a2.length) return false;
for (let i = 0; i < a1.length; i++) {
if (a1[i] !== a2[i]) {
return false;
}
}
return true;
}


type MaybeCleanUpFn = void | (() => void);


function useNumberArrayEffect(cb: () => MaybeCleanUpFn, deps: number[]) {
const ref = useRef<number[]>(deps);


if (!arrayEqual(deps, ref.current)) {
ref.current = deps;
}


useEffect(cb, [ref.current]);
}

用法

function Child({ arr }: { arr: number[] }) {
useNumberArrayEffect(() => {
console.log("run effect", JSON.stringify(arr));
}, arr);


return <pre>{JSON.stringify(arr)}</pre>;
}

更进一步,我们还可以通过创建一个接受自定义等同函数的效果挂钩来重用挂钩。

type MaybeCleanUpFn = void | (() => void);
type EqualityFn = (a: DependencyList, b: DependencyList) => boolean;


function useCustomEffect(
cb: () => MaybeCleanUpFn,
deps: DependencyList,
equal?: EqualityFn
) {
const ref = useRef<DependencyList>(deps);


if (!equal || !equal(deps, ref.current)) {
ref.current = deps;
}


useEffect(cb, [ref.current]);
}

用法

useCustomEffect(
() => {
console.log("run custom effect", JSON.stringify(arr));
},
[arr],
(a, b) => arrayEqual(a[0], b[0])
);

现场演示

Edit 59467758/passing-array-to-useeffect-dependency-list

我建议大家研究一下这个 OSS 包,它是为了解决您所描述的确切问题而创建的(深入比较依赖项数组中的值而不是浅表值) :

Https://github.com/kentcdodds/use-deep-compare-effect

其用法/API 与 useEffect完全相同,但是会进行更深入的比较。

但是,我要提醒您不要在不需要它的地方使用它,因为它有可能导致由于不必要的深度比较而导致的性能下降,而浅度比较可能会导致性能下降。

作为 Loi-nguyen-huynh 的回答的补充,对于任何遇到 详尽的警告的人来说,可以通过首先将字符串化的 JSON 分解成一个变量来解决这个问题:

const depsString = JSON.stringify(deps);
React.useEffect(() => {
...
}, [depsString]);

快速解决方案,虽然有点像黑客:

const [string, setString] = useState('1');


useEffect(() => {
console.log(outcomes)
}, [string])

当你更新 数组的“结果”时,也像这样更新字符串

setString(prev => `${prev}2`)