如何在useEffect中追踪对象引用?解决deps依赖无限循环问题
React 自定义API Hook依赖项无限循环问题解决
问题场景
在构建useReader自定义API Hook时,期望传入的deps数组内容变化时触发useEffect重新执行数据请求,但目前无论直接使用deps、[deps ?? []]还是展开deps的写法,都引发了无限循环。deps支持undefined或any[]类型。
Hook实现代码
export const useReader = <TResponseData>({ url, deps, options }: TypeUseReader<TResponseData>) => { const [data, setData] = useState<TResponseData | null>(null); const [isLoading, setIsLoading] = useState<boolean>(false); const [error, setError] = useState<number | null >(null); async function fetchData(newUrl?: string) {} useEffect(() => { // ..fetch逻辑 // eslint-disable-next-line react-hooks/exhaustive-deps }, [url, deps]); // 此处deps导致无限循环 return { data, isLoading, error, fetchData }; };
Hook使用方式
return useReader<FOO>({ url: 'URL_PLACEHOLDER', deps: [anything] /* FIXME: deps引发无限循环 */ });
问题原因
React的useEffect依赖项通过浅比较判断是否变化:
- 若调用
useReader时直接传入deps: [anything],每次组件渲染都会生成一个新的数组引用,React会认为依赖项发生变化,触发useEffect重新执行,进而导致组件重新渲染,形成循环。 - 直接将
deps数组作为依赖项时,React比较的是数组的引用而非内部元素,只要引用变化就会触发回调。
解决方案
方案1:在Hook内部展开deps元素作为依赖
修改useEffect的依赖数组,将deps的元素逐个加入依赖,而非使用数组本身:
useEffect(() => { // ..fetch逻辑 }, [url, ...(deps ?? [])]);
这样React会直接比较deps数组中的每个元素,而非数组引用。但需注意:若deps中包含引用类型元素(如对象、数组),仍需确保这些元素的引用稳定,否则仍可能引发循环。
方案2:外部传入稳定的deps引用
在调用useReader的组件中,用useMemo包裹deps数组,确保只有内部元素变化时才更新数组引用:
const stableDeps = useMemo(() => [anything], [anything]); return useReader<FOO>({ url: 'URL_PLACEHOLDER', deps: stableDeps });
这种方式从根源上保证了deps引用的稳定性,避免因数组重复创建导致的不必要触发。
额外优化:稳定fetchData函数
原代码中fetchData函数每次渲染都会重新创建,即使添加了eslint忽略,也可能引发潜在问题。可以用useCallback包裹fetchData,并补充完整依赖:
const fetchData = useCallback(async (newUrl?: string) => { // ..fetch逻辑 }, [/* 此处添加fetchData用到的依赖,如setData、setIsLoading等 */]);
然后在useEffect中使用这个稳定的函数,并移除eslint忽略注释。
内容的提问来源于stack exchange,提问作者Mufasa
相关产品推荐
相关产品推荐

