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

React中使用useEffect基于状态控制带延迟的递归fetch请求启停

解决方案

核心问题修复思路

  • 使用useRef存储跨渲染周期的变量:普通组件内变量会在每次重渲染时重置,useRef的current属性可以持久化存储值,且不会触发组件重渲染,适合存储请求实例、定时器ID、最新状态快照
  • 采用标准AbortController实现fetch请求取消:原生fetch不支持直接调用cancel方法,需要绑定控制器的signal参数实现取消
  • 用ref同步状态值解决闭包捕获旧值问题:递归函数因为闭包特性会捕获创建时的状态值,将状态同步到ref后访问current属性即可拿到最新值
  • 修复状态更新异步导致的初始值错误:setState是异步操作,调用后不会立刻更新状态,需要同步更新ref或者直接用接口返回的新值启动递归

修复后完整代码

import { useState, useEffect, useRef } from 'react';

const Component = () => {
    const [executing, setExecuting] = useState(false);
    const [data1, setData1] = useState('default');
    const [data2, setData2] = useState('default');
    const [data3, setData3] = useState('default');

    // 存储AbortController实例,用于取消fetch请求
    const abortControllerRef = useRef(null);
    // 存储定时器ID,用于清理延迟任务
    const timeoutRef = useRef(null);
    // 同步存储data2的最新值,解决闭包旧值问题
    const data2Ref = useRef(data2);

    // 同步data2状态到ref
    useEffect(() => {
        data2Ref.current = data2;
    }, [data2]);

    const recursiveFetch = async () => {
        // 已停止执行则直接终止递归
        if (!executing) return;
        // 取消上一次未完成的请求
        if (abortControllerRef.current) {
            abortControllerRef.current.abort();
        }
        // 新建请求控制器
        const controller = new AbortController();
        abortControllerRef.current = controller;

        try {
            const res = await fetch(`https://website.com/${data2Ref.current}`, {
                method: 'POST',
                signal: controller.signal
            });
            const resData = await res.json();
            // 此处可根据需要更新data3等状态
            setData3(resData);

            // 启动下一次延迟请求
            timeoutRef.current = setTimeout(() => {
                recursiveFetch();
            }, 1000);
        } catch (err) {
            // 忽略主动取消请求的报错
            if (err.name !== 'AbortError') {
                console.error('请求出错:', err);
            }
        }
    };

    useEffect(() => {
        if (executing) {
            // 拉取必要的初始数据
            fetch(`https://example.com/${data1}`)
                .then((res) => res.json())
                .then((data) => {
                    const newData2 = data.exampleProperty;
                    // 同步更新状态和ref
                    setData2(newData2);
                    // 启动递归请求
                    recursiveFetch();
                });
        } else {
            // 执行清理逻辑
            if (abortControllerRef.current) {
                abortControllerRef.current.abort();
            }
            if (timeoutRef.current) {
                clearTimeout(timeoutRef.current);
            }
        }

        // 组件卸载/依赖变更时自动执行清理
        return () => {
            if (abortControllerRef.current) {
                abortControllerRef.current.abort();
            }
            if (timeoutRef.current) {
                clearTimeout(timeoutRef.current);
            }
        };
    }, [executing, data1]);

    return (
        <>
            <button onClick={() => setExecuting((prev) => !prev)}>
                {executing ? '停止请求' : '启动请求'}
            </button>
        </>
    );
};

export default Component;

关键改动说明

  • 新增的三个ref分别解决了请求实例、定时器的持久化存储,以及data2的最新值获取问题,避免了组件重渲染导致的变量丢失
  • 递归函数内增加了执行状态判断,停止后不会继续发起新请求
  • 新增了useEffect的清理函数,避免组件卸载后仍有请求或定时器运行导致的内存泄漏
  • 将data1加入useEffect依赖,保证data1更新后重启请求时使用最新值

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 15:45:01