React中使用setState时Fetch请求无法中止的问题
问题原因分析
你遇到的问题核心在于组件重渲染导致AbortController实例丢失:
- 代码里的
controller和signal是组件函数内部的普通变量,每次调用setStateRefresh触发组件重渲染时,这两个变量会被重新声明,原来的AbortController实例就被垃圾回收了。 - 当你点击stop按钮时,
controller已经是重渲染后的新变量(未赋值),自然无法调用abort()中止之前的请求。
修复方案
使用useRef来保存AbortController实例,因为useRef的current属性在组件整个生命周期内保持不变,不会随重渲染重置。同时优化请求逻辑,避免重复创建请求:
import { useState, useRef } from 'react'; export default function FetchTest() { const [stateRefresh, setStateRefresh] = useState(true); // 用useRef保存controller实例 const controllerRef = useRef(null); const startFetch = async () => { // 先中止之前未完成的请求 if (controllerRef.current) { controllerRef.current.abort(); } // 创建新的AbortController const controller = new AbortController(); controllerRef.current = controller; const signal = controller.signal; // 把var改成let,避免变量提升导致的问题 for (let i = 19; i >= 0; i--) { const url = 'https://jsonplaceholder.typicode.com/todos/1'; fetch(url, { signal }) .then((response) => { // 检查请求是否已被中止,再更新状态 if (!signal.aborted) { setStateRefresh(prev => !prev); console.log("Download complete", response); } }) .catch((err) => { // 忽略中止导致的错误,避免控制台报错 if (err.name !== 'AbortError') { console.error(`Download error: ${err.message}`); } }); } }; const stopFetch = () => { if (controllerRef.current) { controllerRef.current.abort(); // 清空ref,避免重复调用 controllerRef.current = null; } }; return ( <div> <button onClick={startFetch}>start</button> <button onClick={stopFetch}>stop</button> </div> ); }
关键优化点
- useRef存储控制器:确保组件重渲染时不会丢失AbortController的引用,stop操作能精准中止目标请求。
- 前置中止逻辑:每次启动新请求前先中止旧请求,避免多个请求同时运行。
- 检查请求状态:在更新状态前判断
signal.aborted,避免已中止的请求触发不必要的状态更新。 - 忽略中止错误:AbortController中止请求会抛出
AbortError,可以选择性忽略这个错误,减少控制台冗余日志。
内容的提问来源于stack exchange,提问作者John Thapa
相关产品推荐
相关产品推荐

