自定义Hook触发无限循环排查:已设依赖数组仍报错
解决useListGuessers Hook的无限循环问题
问题根源
你遇到的无限循环,核心原因是每次组件渲染时,filteredGuesser都会生成一个全新的数组引用,导致依赖它的useEffect每次都会触发,调用setList后又触发组件重新渲染,形成闭环:
- 每次渲染都会创建新的
DashboardGroups实例 - 基于新实例生成的
guessers是新数组 filteredGuesser是经过过滤、映射、排序后的新数组(引用地址每次都变)useEffect检测到filteredGuesser变化,执行setList更新状态- 状态更新触发组件重新渲染,重复上述步骤
解决方案
方案1:移除不必要的state,直接返回缓存后的计算值
因为list只是filteredGuesser的直接映射,完全不需要用useState存储,直接用useMemo缓存计算结果即可,从根源避免状态更新触发的循环:
export const useListGuessers = () => { const hasMarketing = UserHelper.hasAuthorization(AUTHORIZATION_MARKETING); const hasTechnical = UserHelper.hasAuthorization(AUTHORIZATION_TECHNICAL); // 用useMemo缓存计算结果,仅当权限变化时重新计算 const filteredGuesser = useMemo(() => { const dashboardGroups = new DashboardGroups({hasMarketing, hasTechnical}); const guessers = [ ...dashboardGroups.appProductGroup(), ...dashboardGroups.articlesGroup(), ...dashboardGroups.mediasGroup(), ...dashboardGroups.productsGroup(), ...dashboardGroups.orderableProductsGroup(), ...dashboardGroups.typesGroup(), ...dashboardGroups.usersGroup(), ...dashboardGroups.othersGroup(), ...dashboardGroups.userManagementGroup(), ]; return guessers .filter(({canShow}) => canShow) .map((guesser: any) => ({ label: guesser.label ?? guesser.value.options.label, link: (guesser.operation && `user-management/${guesser.operation}`) ?? guesser.value.name, })) .sort((a, b) => a.label.localeCompare(b.label)); }, [hasMarketing, hasTechnical]); // 依赖权限值,仅当权限变化时重新计算 return filteredGuesser; };
方案2:保留state但缓存依赖项
如果一定要用useState存储list,需要用useMemo缓存filteredGuesser,确保只有当权限变化时它的引用才会改变:
export const useListGuessers = () => { const [list, setList] = useState([]); const hasMarketing = UserHelper.hasAuthorization(AUTHORIZATION_MARKETING); const hasTechnical = UserHelper.hasAuthorization(AUTHORIZATION_TECHNICAL); // 缓存filteredGuesser,避免每次渲染生成新引用 const filteredGuesser = useMemo(() => { const dashboardGroups = new DashboardGroups({hasMarketing, hasTechnical}); const guessers = [ ...dashboardGroups.appProductGroup(), ...dashboardGroups.articlesGroup(), ...dashboardGroups.mediasGroup(), ...dashboardGroups.productsGroup(), ...dashboardGroups.orderableProductsGroup(), ...dashboardGroups.typesGroup(), ...dashboardGroups.usersGroup(), ...dashboardGroups.othersGroup(), ...dashboardGroups.userManagementGroup(), ]; return guessers .filter(({canShow}) => canShow) .map((guesser: any) => ({ label: guesser.label ?? guesser.value.options.label, link: (guesser.operation && `user-management/${guesser.operation}`) ?? guesser.value.name, })) .sort((a, b) => a.label.localeCompare(b.label)); }, [hasMarketing, hasTechnical]); useEffect(() => { setList(filteredGuesser); }, [filteredGuesser]); // 现在只有权限变化时才会触发 return list; };
关键说明
useMemo的作用是缓存计算结果,只有当依赖数组中的值变化时,才会重新执行回调函数生成新值- 数组、对象等引用类型,即使内容相同,每次重新创建都会有新的引用地址,React的依赖对比是浅对比,所以会被判定为变化
- 优先选择方案1,因为不需要额外的状态存储,逻辑更简洁,性能更好
内容的提问来源于stack exchange,提问作者Johan
相关产品推荐
相关产品推荐

