React 异步状态更新“隐形炸弹”:从警告到内存泄漏的全链路排雷
·
React 异步状态更新“隐形炸弹”:从警告到内存泄漏的全链路排雷
当你看到
Can't perform a React state update on an unmounted component
或发现接口返回后页面没反应、内存暴涨——99% 是「异步操作里更新状态」没处理好。
本文用「原理 → 场景 → 修复」三步法,带你一次性解决 React 异步状态更新的所有暗坑。
一、为什么异步更新容易翻车?
React 生命周期:
- 组件已卸载 → 所有引用被清理;
- 异步回调(Promise、定时器、事件)仍持有
setState; - 调用
setState→ React 抛出警告,甚至内存泄漏。
二、4 个高频翻车现场 & 修复
1️⃣ 组件卸载后仍更新(最经典)
function User({ id }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/user/${id}`)
.then(r => r.json())
.then(setUser); // ❌ 组件切换路由后仍回调
}, [id]);
return <div>{user?.name}</div>;
}
修复:AbortController 或取消标记
useEffect(() => {
const controller = new AbortController();
fetch(`/api/user/${id}`, { signal: controller.signal })
.then(r => r.json())
.then(setUser)
.catch(() => {}); // 忽略已取消请求
return () => controller.abort(); // ✅ 清理
}, [id]);
2️⃣ 定时器未清理
function Timer() {
const [sec, setSec] = useState(0);
useEffect(() => {
const id = setInterval(() => setSec(s => s + 1), 1000); // ❌ 卸载后仍执行
}, []);
return <span>{sec}</span>;
}
修复:清除定时器
useEffect(() => {
const id = setInterval(() => setSec(s => s + 1), 1000);
return () => clearInterval(id); // ✅
}, []);
3️⃣ 链式 Promise 未返回,后续 then 失控
fetch('/api/data')
.then(res => {
if (!res.ok) throw new Error('fail');
return res.json(); // ❌ 忘记 return
})
.then(setData) // 永远不会执行
修复:确保每一步都 return
fetch('/api/data')
.then(res => (res.ok ? res.json() : Promise.reject(res)))
.then(setData)
.catch(console.error);
4️⃣ 并行请求竞态(后点先回)
function Search({ keyword }) {
const [results, setResults] = useState([]);
useEffect(() => {
fetchResults(keyword).then(setResults); // ❌ 先点的请求后返回,覆盖正确数据
}, [keyword]);
return <List data={results} />;
}
修复:只保留最新一次请求
useEffect(() => {
let ignore = false;
fetchResults(keyword).then(res => {
if (!ignore) setResults(res); // ✅ 忽略过期回调
});
return () => (ignore = true);
}, [keyword]);
三、通用「已卸载」检测 Hook
import { useRef, useEffect } from 'react';
export function useIsMounted() {
const isMountedRef = useRef(true);
useEffect(() => () => (isMountedRef.current = false), []);
return isMountedRef;
}
使用:
const isMounted = useIsMounted();
fetchData().then(res => {
if (isMounted.current) setData(res);
});
四、现代库方案(推荐)
| 库 | 用法 | 效果 |
|---|---|---|
| react-query | useQuery(['user', id], fetchUser) | 自动取消、重试、缓存 |
| axios | const source = axios.CancelToken.source() | 原生取消 token |
| ahooks | useRequest(fn, { ready: condition }) | 内置竞态处理 |
五、一句话总结
任何异步回调里更新 state 前,先问:组件还在吗?
用「取消控制器 + ignore 标记 + 现代库」三件套,彻底告别警告与内存泄漏。
最后问候亲爱的朋友们,并邀请你们阅读我的全新著作
📚 《 React开发实践:掌握Redux与Hooks应用 》

更多推荐
所有评论(0)