React模态框卸载后while循环未终止的原因咨询
模态框卸载后while循环仍持续运行的原因及解决方法
问题概述
在模态框组件内用while循环实现轮询逻辑,关闭模态框(组件卸载)后,控制台日志仍持续输出,循环并未终止。原以为条件渲染的组件卸载后循环会自动中止,疑惑为何循环还在运行。
相关代码
import React from "react"; import "./styles.css"; const Modal = () => { const wait = async (ms = 1000) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; let count = 0; const pollIncrement = async () => { while (count < 100) { await wait(2000); console.log(++count); } return count; }; React.useEffect(() => { pollIncrement(); }, []); return ( <div style={{ background: "blue", color: "white", width: "400px", height: "400px" }} > {count} </div> ); }; export default function App() { const [isModalOpen, setIsModalOpen] = React.useState(false); return ( <> <button onClick={() => setIsModalOpen(!isModalOpen)}>open modal</button> {isModalOpen && <Modal />} </> ); }
原因分析
React组件卸载只会清理自身的React相关资源(比如状态、useEffect的清理逻辑等),但无法直接终止已经启动的异步任务或循环。你在useEffect中调用的pollIncrement函数,一旦开始执行就会进入异步等待流程(await wait(2000)),这个异步任务会被放入JavaScript的事件队列中,即使组件卸载,队列里的任务仍会按照既定逻辑执行,直到循环条件不满足。
简单来说:组件卸载不影响已经运行的异步代码,循环会继续执行,因为JavaScript的异步任务不受React组件生命周期的直接管控。
解决方法
要在组件卸载时终止循环,需要添加终止标识,并借助useEffect的清理函数触发标识,让循环条件失效。
方案1:使用布尔终止标识
修改后的Modal组件代码:
const Modal = () => { const wait = async (ms = 1000) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; let count = 0; // 定义终止标识 let isUnmounted = false; const pollIncrement = async () => { // 将终止标识加入循环判断条件 while (count < 100 && !isUnmounted) { await wait(2000); // 等待结束后再检查一次,避免等待期间组件已卸载 if (isUnmounted) break; console.log(++count); } return count; }; React.useEffect(() => { pollIncrement(); // 组件卸载时设置终止标识 return () => { isUnmounted = true; }; }, []); return ( <div style={{ background: "blue", color: "white", width: "400px", height: "400px" }} > {count} </div> ); };
方案2:使用AbortController(更适合真实API请求场景)
AbortController是浏览器提供的专门用于终止异步任务的API,能更可靠地中断请求或定时器:
const Modal = () => { const wait = (ms = 1000, signal) => { return new Promise((resolve, reject) => { const timeout = setTimeout(resolve, ms); // 监听中止信号,触发时清除定时器并拒绝Promise signal.addEventListener('abort', () => { clearTimeout(timeout); reject(new Error('Polling aborted')); }); }); }; let count = 0; const pollIncrement = async (signal) => { try { while (count < 100) { await wait(2000, signal); console.log(++count); } } catch (err) { if (err.name === 'AbortError') { console.log('Polling stopped'); } } return count; }; React.useEffect(() => { const controller = new AbortController(); pollIncrement(controller.signal); // 组件卸载时发送中止信号 return () => { controller.abort(); }; }, []); return ( <div style={{ background: "blue", color: "white", width: "400px", height: "400px" }} > {count} </div> ); };
内容的提问来源于stack exchange,提问作者gywn.olivia
相关产品推荐
相关产品推荐

