React应用如何解决切出标签页后返回卡顿甚至崩溃的问题?
问题原因
浏览器会对非激活状态的标签页做定时器节流,setInterval的回调触发频率会被限制到最低1秒/次甚至暂停,所有被阻塞的回调会在你切回标签页时批量触发。加上你原有逻辑用的是固定间隔的setInterval,不关心上一次请求是否完成,就会导致页面隐藏期间累积大量未完成的请求,切回后批量响应同时触发多次setState重渲染,占满浏览器主线程导致卡顿甚至崩溃。
修复方案
- 把固定间隔的
setInterval替换为请求完成后再调度下一次的递归setTimeout,从根源避免请求重叠堆积 - 监听页面
visibilitychange事件,页面隐藏时暂停轮询,页面重新显示时立刻拉取一次数据再恢复轮询 - 把三次独立的
setState合并为一次调用,减少不必要的重复渲染 - 新增请求取消逻辑,组件卸载/页面隐藏时中断未完成的无效请求
修改后代码示例
componentDidMount() { // 监听页面可见性变化 document.addEventListener('visibilitychange', this.handleVisibilityChange); // 启动轮询 this.startPolling(); } componentWillUnmount() { // 清理定时器 clearTimeout(this.pollingTimer); // 移除事件监听 document.removeEventListener('visibilitychange', this.handleVisibilityChange); // 取消未完成的请求 if (this.requestController) { this.requestController.abort(); } } handleVisibilityChange = () => { if (document.hidden) { // 页面隐藏,清除定时器暂停轮询,取消当前请求 clearTimeout(this.pollingTimer); if (this.requestController) { this.requestController.abort(); } } else { // 页面显示,立刻拉一次数据再恢复轮询 this.getSelPos(); } } startPolling = () => { // 页面可见才调度下一次请求 if (!document.hidden) { this.pollingTimer = setTimeout(() => { this.getSelPos(); }, 2000); } } async getSelPos() { try { // 创建取消控制器 this.requestController = new AbortController(); const response = await axios.get("http://localhost:5000/selector", { crossdomain: true, signal: this.requestController.signal }); // 合并setState,仅触发一次渲染 this.setState({ SelectorPos: parseInt(response.data.selector), Pressure: parseInt(response.data.pressure), Temperature: parseInt(response.data.temperature) }); } catch (err) { // 忽略请求取消的报错 if (!axios.isCancel(err)) { console.error('请求失败', err); } } finally { // 请求完成后调度下一次轮询 this.startPolling(); } }
可选优化
如果对数据实时性要求更高,可以把轮询方案替换为WebSocket推送,避免轮询带来的性能损耗和延迟问题。
内容的提问来源于stack exchange,提问作者Cm1602
相关产品推荐
相关产品推荐

