You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义Hook触发无限循环排查:已设依赖数组仍报错

解决useListGuessers Hook的无限循环问题

问题根源

你遇到的无限循环,核心原因是每次组件渲染时,filteredGuesser都会生成一个全新的数组引用,导致依赖它的useEffect每次都会触发,调用setList后又触发组件重新渲染,形成闭环:

  1. 每次渲染都会创建新的DashboardGroups实例
  2. 基于新实例生成的guessers是新数组
  3. filteredGuesser是经过过滤、映射、排序后的新数组(引用地址每次都变)
  4. useEffect检测到filteredGuesser变化,执行setList更新状态
  5. 状态更新触发组件重新渲染,重复上述步骤

解决方案

方案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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 01:40:38