ReactJS抽离数据获取逻辑后如何编写useEffect清理函数
useEffect抽离请求函数后的清理逻辑实现
你当前代码的问题
你现在写的清理逻辑是完全无效的:mountedTodos变量定义在useEffect的回调作用域里,抽离出去的getTodos函数根本访问不到这个变量,哪怕组件卸载时你把mountedTodos设为false,getTodos里的逻辑完全感知不到,等请求返回后依然会执行setTodos,还是会触发React的内存泄漏警告。
推荐实现方案
方案1:使用原生AbortController(最优解)
这个方案不仅能避免卸载后更新状态,还能直接中断未完成的网络请求,比单纯的挂载标记更合理,抽离的请求函数也能保持很好的复用性:
// 抽离的请求函数接收AbortSignal作为入参,只负责数据获取,不耦合组件状态 const getTodos = async (signal) => { try { // 将signal传入fetch配置,中断请求时会自动抛出AbortError const response = await fetch('https://jsonplaceholder.typicode.com/todos/1', { signal }); return await response.json(); } catch(e) { // 主动中断的请求不需要打印错误 if (e.name !== 'AbortError') { console.log(e); } return null; } } useEffect(() => { const controller = new AbortController(); // 调用请求时传入控制信号 getTodos(controller.signal).then(todos => { if (todos) { setTodos(todos); } }); return () => { // 组件卸载时直接中断请求 controller.abort(); }; }, []);
这个方案的优势:
- 真正中断未完成的网络请求,节省流量
- 请求函数和组件状态解耦,可直接复用到其他组件
- 不需要依赖闭包变量做判断,逻辑更清晰
方案2:拆分职责,把状态更新逻辑留在useEffect作用域
如果你不想引入AbortController,只要调整抽离逻辑的边界就行:抽离的函数只负责纯数据请求,所有和组件状态相关的setState操作,全部放回useEffect内部执行,这样就能和你最开始写的内联请求逻辑一样,正常访问挂载标记:
// 抽离纯请求逻辑,不做任何状态更新 const getTodos = async () => { const response = await fetch('https://jsonplaceholder.typicode.com/todos/1'); return response.json(); } useEffect(() => { let isMounted = true; getTodos() .then(todos => { // 这里能正常访问到isMounted标记 if (isMounted && todos) { setTodos(todos); } }) .catch(e => console.log(e)); return () => { isMounted = false; }; }, []);
常见踩坑提醒
- 不要给async函数传普通布尔类型的挂载标记:基础类型是值传递,函数内部拿到的永远是调用那一刻的
true,根本感知不到后续标记变为false的变化,判断完全失效。 - 不要给useEffect的回调函数加async关键字:async函数默认返回Promise,React无法识别你return的清理函数,会导致清理逻辑直接失效。
内容的提问来源于stack exchange,提问作者Jc John
相关产品推荐
相关产品推荐

