useEffect - 依赖对象或数组

useEffect 依赖项为对象或数组时,引发不断重渲染问题的原因及解法。

背景

今天封了个轮子,对组件传入参数生成对应的动画实例,当入参变化时重新渲染新实例。自然而然想到的就是 useEffect 监听 props 依赖了,如:useEffect(() => { ... }, [props]);。然而遇到了问题,明明依赖项 props 没有变化,却还是在不停地重渲染。

😓

原因

React 中各 Hooks 函数依赖数组会将依赖值与上次渲染依赖中的值做对比,如果相等则不需要重新渲染,否则重渲染。为了性能考虑,此处的对比是浅比较,即 Object.is(arg1, arg2)。对于基本类型没有问题,而组件每次渲染时传入的引用类型都会重新开辟一个全新的引用地址,这时候做浅比较就不等了,从而会触发渲染。如果在 useEffect 中修改了引用类型,则必然会引发无限渲染的问题。

解法

解法一

了解完原理后,脑子里 0.1s 闪过的解法就是 JSON.stringify() 了。真好使,深浅比较问题一解一个爽。燃鹅理性告诉我,不能这么写,方法是好方法,就怕同行看到笑我是傻X。

useEffect(() => {
...
}, [JSON.stringify(object)]);

老李:你TND还真是个天才…

解法二

可以基于 useRef 和深比较方法来解,useRef 的特性是跨渲染周期缓存数据。此处用来缓存上一次渲染的数据,并调用深比较方法判断,如果两个对象相等则返回上一次的数据,地址自然也没有变化。

import { isEqual } from 'lodash';

const useCampare = (value: any, compare: any) => {
const ref = useRef<any>(null);

if (!compare(value, ref.current)) { // deep compare
ref.current = value;
}

return ref.current;
}

const compareObject = useCampare(object, isEqual);

useEffect(() => {
...
}, [compareObject]);

解法三

封装一个基于深比较的 useEffect 方法。

type DeepIsEqualType<TDeps = React.DependencyList> = (newDeps: TDeps, oldDeps: TDeps) => boolean;

export const useDeepEqualEffect<TDeps = React.DependencyList> = (
effect: React.EffectCallback,
deps: TDeps,
compare: DeepIsEqualType<TDeps> = isEqual
) => {
const oldDeps = React.useRef<TDeps | undefined>(undefined);
if (!oldDeps.current || !compare(deps, oldDeps.current as TDeps)) {
oldDeps.current = deps;
}

React.useEffect(effect, [oldDeps.current]);
}

useDeepEqualEffect(() => {
...
}, [object]);

解法四

使用三方基于深比较的 useEffect 库,对性能还是有一定影响的,非必须不用。

import useDeepCompareEffect from 'use-deep-compare-effect';

useDeepCompareEffect(() => {
...
}, [object]);
查看评论