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

React自定义Hook引发重复渲染(Maximum Depth Exceeded)问题排查

解决React自定义Hook结合RTK Query的无限重渲染问题

问题根源

你的代码陷入无限重渲染循环的核心原因有两点:

  1. RTK Query返回的data引用不稳定:即使服务器返回的数据内容没有变化,RTK Query每次返回的data对象/数组可能是新的引用(尤其是组件重渲染时,若查询参数引用变化或缓存机制细节导致)。
  2. 无意义的状态更新触发循环:你在useEffect中调用setTransactions([]),哪怕当前transactions已经是空数组。由于[]是新创建的数组引用,React会判定状态发生变化,触发组件重渲染。重渲染后又会重新执行useGetTransactionsQuery,得到新的data引用,再次触发useEffect,形成无限循环,最终导致Maximum Depth Exceeded错误。

另外你提到的“首次加载后isLoading始终为true”,大概率是混淆了RTK Query的isLoading和isFetching:isLoading仅在首次请求未完成时为true,后续分页请求应该用isFetching判断加载状态。

解决方案

方案1:移除不必要的本地状态同步

如果不需要对RTK Query返回的数据做本地聚合(比如分页合并),直接使用RTK Query提供的data即可,完全不需要本地transactions状态:

export default function useTransactions(accountId) {
    const [page, setPage] = useState(1);

    const {
        data = { data: [] },
        isSuccess,
        isFetching, // 用isFetching判断后续加载状态
    } = useGetTransactionsQuery({ accountId, page });

    const handleMoreTransactionsClick = () => {
        setPage(prev => prev + 1);
    };

    const handleDeleteTransaction = (transactionId) => {
        // 调用你的删除mutation,RTK Query会自动更新缓存
        // const [deleteTransaction] = useDeleteTransactionMutation();
        // deleteTransaction(transactionId);
    };

    return {
        transactions: data.data,
        handleMoreTransactionsClick,
        handleDeleteTransaction,
        isLoading: isFetching
    };
}

方案2:正确实现分页数据聚合(保留本地状态)

如果需要将多页数据聚合到本地transactions中,需要优化useEffect的触发条件,避免无意义的状态更新:

export default function useTransactions(accountId) {
    const [page, setPage] = useState(1);
    const [transactions, setTransactions] = useState([]);

    const {
        data = { data: [] },
        isSuccess,
        isFetching,
        isError
    } = useGetTransactionsQuery({ accountId, page });

    // 仅当新页数据加载成功,且数据内容有变化时,合并到本地状态
    useEffect(() => {
        if (isSuccess && data.data.length > 0) {
            // 使用函数式更新,确保基于最新的prevState操作
            setTransactions(prev => {
                // 避免重复添加相同数据(可选,根据接口返回逻辑调整)
                const newIds = new Set(data.data.map(item => item.id));
                return [...prev, ...data.data.filter(item => !prev.some(p => p.id === item.id))];
            });
        }
    }, [data.data, isSuccess]); // 依赖具体的data.data,而非整个data对象

    const handleMoreTransactionsClick = () => {
        if (!isFetching) {
            setPage(prev => prev + 1);
        }
    };

    const handleDeleteTransaction = (transactionId) => {
        // 调用删除mutation后,本地状态同步删除(或依赖RTK Query缓存自动更新)
        setTransactions(prev => prev.filter(item => item.id !== transactionId));
        // const [deleteTransaction] = useDeleteTransactionMutation();
        // deleteTransaction(transactionId);
    };

    return {
        transactions,
        handleMoreTransactionsClick,
        handleDeleteTransaction,
        isLoading: isFetching
    };
}

方案3:修复无意义的空数组赋值问题

针对你简化代码中的场景,如果确实需要重置transactions为空数组,需要添加条件判断,避免重复触发状态更新:

useEffect(() => {
    // 仅当当前transactions不是空数组时,才执行重置
    if (transactions.length !== 0) {
        setTransactions([]);
    }
}, [data, transactions]);

额外注意事项

  • RTK Query的查询参数尽量使用稳定的引用:比如将{accountId, page}用useMemo缓存,避免每次组件渲染时创建新对象导致不必要的缓存检查:
    const queryArg = useMemo(() => ({ accountId, page }), [accountId, page]);
    const { data, isSuccess } = useGetTransactionsQuery(queryArg);
    
  • 利用RTK Query的缓存标签(providesTags/invalidatesTags)自动管理数据更新,比如删除交易后,让RTK Query自动重新获取数据,减少本地状态的手动维护。

内容的提问来源于stack exchange,提问作者maganthro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:44:53