React从状态/Hook取数后发起异步调用触发无限循环如何解决
问题根因
你的判断完全准确,无限循环的触发链路是:
- 组件重渲染时,
useFarmInfo、useDualFarmInfo每次都会返回全新引用的对象/数组,即使内容和上一次渲染完全一致 - 引用变化导致
useMemo判定依赖更新,重新生成新的pairLists数组 pairLists引用变化触发useEffect执行,调用接口后setBulkPairs更新状态,再次触发组件重渲染,回到第一步形成死循环
另外注意:useMemo仅适合同步计算值缓存,异步请求属于副作用,不能放在useMemo中执行,会引发渲染时序异常。
修复步骤
1. 先修复自定义Hook返回值引用不稳定的问题(核心修复)
给自定义Hook的返回值加缓存,保证内容不变时引用不发生变化:
export function useFarmInfo(): { [chainId in ChainId]: StakingBasic[]; } { const defaultFarms = useDefaultFarmList(); // 缓存对应链的农场数组,仅当源数据变化时重新生成 const maticFarms = useMemo( () => Object.values(defaultFarms[ChainId.MATIC]), [defaultFarms] ); // 缓存整个返回对象,避免每次渲染生成新对象引用 return useMemo( () => ({ [ChainId.MATIC]: maticFarms, [ChainId.MUMBAI]: [], }), [maticFarms] ); }
useDualFarmInfo按照同样的逻辑,给内部生成的数组、返回的外层对象加useMemo缓存即可。
2. 优化pairLists的依赖范围
不要把整个农场对象作为依赖,只取当前链实际用到的农场列表作为依赖,减少不必要的更新触发:
// 直接取当前链对应的农场列表,不持有整个对象的依赖 const currentLpFarms = lpFarms[chainIdOrDefault]; const currentDualFarms = dualFarms[chainIdOrDefault]; const pairLists = useMemo(() => { const stakingPairLists = currentLpFarms.map((item) => item.pair); const dualPairLists = currentDualFarms.map((item) => item.pair); return stakingPairLists.concat(dualPairLists); }, [chainIdOrDefault, currentLpFarms, currentDualFarms]);
3. 给异步请求加过期守卫,避免无效更新
就算引用完全稳定,也建议给useEffect里的异步逻辑加竞态处理,避免组件卸载、请求过期后错误更新状态:
useEffect(() => { // 标记当前请求是否有效 let isActive = true; // 空列表直接跳过请求,重置状态即可 if (pairLists.length === 0) { setBulkPairs({}); return () => { isActive = false; }; } getBulkPairData(pairLists).then((data) => { // 仅当请求有效时才更新状态 if (isActive) { setBulkPairs(data); } }); // 卸载或依赖更新时标记当前请求失效 return () => { isActive = false; }; }, [pairLists]);
额外优化建议
如果接口返回的数据结构稳定,可以在setBulkPairs前加一层关键字段对比或者深比较,如果返回数据和现有状态完全一致就不触发setState,进一步减少多余重渲染。
内容的提问来源于stack exchange,提问作者Ritzy Dev
相关产品推荐
相关产品推荐

