使用React useEffect钩子时如何检测依赖项中的对象是否发生变更
问题根因
useEffect的依赖对比默认是引用相等比较,如果每次调用useFetch时传入的param都是新生成的对象(比如父组件直接写useFetch({ page: 1, size: 10 }),每次渲染都会创建新的对象引用),就会被判定为依赖变更,反复触发effect逻辑,最终形成无限循环。
可行解决办法
- 方法1:把对象属性拆解为基本类型依赖
如果param的属性是固定可枚举的,可以直接把依赖项替换为对象的各个基础类型属性,避免对对象整体做引用对比:
const useFetch = (param?: { page?: number; size?: number }) => { const [count, setCount] = useState<string>(""); useEffect(() => { // 发起数据请求 setCount("1"); }, [param?.page, param?.size]) // 拆解为基础类型属性作为依赖 return count; }
同时父组件调用时也可以尽量传单个基础类型参数,减少对象传参的场景。
- 方法2:对对象做深比较判断是否变更
如果param的结构不固定,没法拆解属性,可以使用useDeepCompareEffect代替原生useEffect,内部会对依赖做深值对比,引用不同但值相同的对象不会触发effect:
你可以自己封装一个简易的深比较版本:
import { isEqual } from 'lodash'; import { useEffect, useRef } from 'react'; const useDeepCompareEffect = (callback: () => void, dependencies: any[]) => { const ref = useRef<any[]>([]); // 深对比前后依赖是否一致 if (!isEqual(dependencies, ref.current)) { ref.current = dependencies; } useEffect(callback, [ref.current]); } // 业务中使用 const useFetch = (param?: object) => { const [count, setCount] = useState<string>(""); useDeepCompareEffect(() => { // 发起数据请求 setCount("1"); }, [param]) return count; }
注意如果是自己实现深比较,要注意循环引用等边界场景,避免出现性能问题。
- 方法3:稳定对象引用
如果参数不需要频繁变更,可以在父层用useMemo缓存param对象,保证引用不变,只有当属性真正变更时才更新引用:
// 父组件调用侧 const Parent = () => { const [page, setPage] = useState(1); // 缓存param,只有page变化时才生成新对象 const fetchParam = useMemo(() => ({ page, size: 10 }), [page]); const data = useFetch(fetchParam); return <div>{data}</div> }
性能注意点
深比较本身有额外性能开销,如果对象层级很深、属性很多,优先选择拆解依赖或者稳定引用的方案,尽量不要高频使用深比较hook。
内容的提问来源于stack exchange,提问作者user16836342
相关产品推荐
相关产品推荐

