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

React中useEffect请求API时如何避免竞态条件?

解决React Hook中请求竞态条件的问题

你的问题出在running状态的闭包陷阱上:当getData异步执行时,它捕获的是函数创建时的running值,后续useEffect清理函数调用setRunning(false)更新的是新状态,但旧的getData里的running还是原来的true,导致判断逻辑无法阻止旧请求的响应更新状态。

下面提供两种可靠的解决方案:

方案一:使用AbortController取消请求(推荐)

这是最合理的方案,不仅能阻止旧请求的状态更新,还能直接取消未完成的HTTP请求,节省资源。现代浏览器和Axios(你的instance应该是Axios实例)都支持AbortController。

修改后的代码:

import { useEffect, useState } from "react";
import { instance } from "../src/App";

export const useInstance = (url) => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const controller = new AbortController();
        const signal = controller.signal;

        const getData = async () => {
            setLoading(true);
            setError(null); // 切换URL时清空之前的错误
            try {
                const response = await instance.get(url, { signal }); // 传入signal
                setData(response.data);
                setLoading(false);
            } catch (error) {
                // 忽略取消请求的错误
                if (error.name !== 'CanceledError') {
                    setError(error.message);
                    setLoading(false);
                }
            }
        };

        getData();

        // 清理函数:取消当前请求
        return () => controller.abort();
    }, [url]);

    return [data, error, loading];
};

原理说明:

  • 每次URL变化时,创建新的AbortController,并将其signal传入Axios请求配置。
  • 当组件卸载或URL切换时,执行清理函数调用controller.abort(),Axios会立即取消请求,并抛出CanceledError,我们在catch中忽略这个错误,就不会更新状态了。

方案二:使用Ref跟踪请求有效性

如果你的请求库不支持AbortController,可以用React的useRef来跟踪当前请求是否有效,因为ref的更新是同步的,不会有闭包陷阱。

修改后的代码:

import { useEffect, useState, useRef } from "react";
import { instance } from "../src/App";

export const useInstance = (url) => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    const isCurrentRequest = useRef(true); // 用ref跟踪当前请求是否有效

    useEffect(() => {
        isCurrentRequest.current = true; // URL变化时标记当前请求有效

        const getData = async () => {
            setLoading(true);
            setError(null);
            try {
                const response = await instance.get(url);
                // 只有当前请求是有效的,才更新状态
                if (isCurrentRequest.current) {
                    setData(response.data);
                    setLoading(false);
                }
            } catch (error) {
                if (isCurrentRequest.current) {
                    setError(error.message);
                    setLoading(false);
                }
            }
        };

        getData();

        // 清理函数:标记当前请求无效
        return () => {
            isCurrentRequest.current = false;
        };
    }, [url]);

    return [data, error, loading];
};

原理说明:

  • useRef创建的isCurrentRequest在组件生命周期内保持同一个引用,更新其current属性是同步的,不会触发重新渲染。
  • 每次URL变化时,先将isCurrentRequest.current设为true,表示当前请求是有效的。
  • 当请求完成后,先检查isCurrentRequest.current是否为true,只有是true时才更新状态。
  • 清理函数将isCurrentRequest.current设为false,旧请求完成时就会跳过状态更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 10:00:55