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

React中useEffect内fetch函数被调用两次的问题求助

问题:React中useEffect内的fetch被调用两次

我在React应用里碰到个问题:useEffect钩子里的fetch函数会被调用两次,搞不懂原因。相关代码如下:

useEffect(() => {
    if (auth.userDTO && router.isReady) {
        let url = paymentId ? `user/tickets?paymentId=${paymentId}` : 'user/tickets';
        fetchWithBaseUrl(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                Authorization: 'Bearer ' + auth.userDTO.token,
            },
        }).then(async (response) => {
            if (response.ok) {
                const data = await response.json();
                setMyTickets(data);
            }
        });
    }
}, [auth.userDTO, router.isReady]);

我已经把auth.userDTO和router.isReady设为useEffect的依赖项,但某些场景下fetch还是会触发两次。

我自己琢磨了两种可能:

  • 初始渲染:fetch在初始渲染时调用,这是预期的。
  • 依赖变化:如果auth.userDTO或router.isReady在组件生命周期里发生变化,useEffect会再次执行,导致fetch多调用一次。

我想知道有没有办法避免这次额外的fetch调用,同时保证只有当auth.userDTO和router.isReady都为真值时才执行fetch。有没有人遇到过类似问题?该怎么解决?有哪些最佳实践或优化方案能避免这种情况?


解决方案

1. 排查依赖的“隐性变化”

你看到的两次调用,大概率是因为auth.userDTO每次都是新对象(比如每次登录后返回的是新的引用),哪怕内容没变化,React也会判定依赖更新,触发useEffect。可以在依赖里只监听必要的字段(比如auth.userDTO.token),减少不必要的触发:

// 只监听token而不是整个userDTO
useEffect(() => {
    if (auth.userDTO?.token && router.isReady) {
        let url = paymentId ? `user/tickets?paymentId=${paymentId}` : 'user/tickets';
        fetchWithBaseUrl(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                Authorization: 'Bearer ' + auth.userDTO.token,
            },
        }).then(async (response) => {
            if (response.ok) {
                const data = await response.json();
                setMyTickets(data);
            }
        });
    }
}, [auth.userDTO?.token, router.isReady]);

2. 添加请求去重逻辑

如果依赖确实会变化,但你不想重复发起请求,可以用状态记录是否已经完成过有效请求:

const [hasFetched, setHasFetched] = useState(false);

useEffect(() => {
    if (auth.userDTO && router.isReady && !hasFetched) {
        let url = paymentId ? `user/tickets?paymentId=${paymentId}` : 'user/tickets';
        fetchWithBaseUrl(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                Authorization: 'Bearer ' + auth.userDTO.token,
            },
        }).then(async (response) => {
            if (response.ok) {
                const data = await response.json();
                setMyTickets(data);
                setHasFetched(true);
            }
        });
    }
    // 依赖变化时重置状态,允许重新请求
    return () => setHasFetched(false);
}, [auth.userDTO, router.isReady]);

3. 检查StrictMode的影响

如果你的应用在React StrictMode下运行,开发环境中useEffect会被执行两次(这是React的故意设计,用来检测副作用问题)。生产环境下不会有这个问题,你可以先注释掉StrictMode测试,如果两次调用消失了,那就是这个原因,无需修改代码。

最佳实践

  • 尽量避免在依赖中传递复杂对象,优先监听基本类型值(比如ID、token)。
  • 对于网络请求,用AbortController处理竞态问题,避免依赖变化时旧请求覆盖新请求的结果:
useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    if (auth.userDTO && router.isReady) {
        let url = paymentId ? `user/tickets?paymentId=${paymentId}` : 'user/tickets';
        fetchWithBaseUrl(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                Authorization: 'Bearer ' + auth.userDTO.token,
            },
            signal,
        }).then(async (response) => {
            if (response.ok) {
                const data = await response.json();
                setMyTickets(data);
            }
        }).catch(err => {
            if (err.name !== 'AbortError') {
                // 处理其他错误
            }
        });
    }

    return () => controller.abort();
}, [auth.userDTO, router.isReady]);

内容的提问来源于stack exchange,提问作者János

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 23:30:22