React中window.addEventListener绑定点击事件导致浏览器冻结问题
浏览器冻结的根本原因
你的代码触发了指数级增长的重渲染死循环,逻辑链路是:
- React函数组件每次重渲染时,整个函数体内的代码都会从头执行一遍。你把
window.addEventListener直接写在了组件函数顶层,意味着组件每渲染一次,就会给window新增一个click事件监听,从来不会移除旧的监听。 - 首次挂载时,window上绑定了第一个click监听,点击触发
setState→组件重渲染→新增第二个click监听。 - 下一次点击时,两个监听同时触发,连续执行两次
setState→触发两轮重渲染→新增两个click监听,此时window上一共堆了4个监听。 - 监听数量、重渲染次数会随着点击指数级暴涨,几轮下来就会占满浏览器主线程,直接导致页面冻结。
- 额外隐患:你直接拿闭包里捕获的
window_touched变量计算新值,当多个监听同时触发时,很容易拿到过期的旧值,计数也会不准。
修复方案
需要做两处核心调整:
- 把事件绑定逻辑移到
useEffect里,传入空依赖数组,保证事件只在组件首次挂载时绑定一次,同时在effect的返回值里写卸载逻辑,组件销毁时移除对应监听,避免内存泄漏。 - 当新state依赖旧state值时,使用函数式更新写法,保证每次计算都能拿到最新的state值,不会受闭包旧值影响。
修复后的完整代码:
import "./App.css"; import { useState, useEffect } from "react"; function App() { // 修正了原代码里的拼写错误:thouched -> touched,改用React社区通用的小驼峰命名 const [windowTouched, setWindowTouched] = useState(0); useEffect(() => { const handleWindowClick = () => { // 函数式更新,始终拿到前一次的最新state值 setWindowTouched(prevCount => prevCount + 1); }; // 挂载时绑定一次监听 window.addEventListener("click", handleWindowClick); // 卸载时清理监听 return () => { window.removeEventListener("click", handleWindowClick); }; }, []); // 空依赖代表effect仅在挂载、卸载时执行 return ( <div className="App"> {`This window has been touched ${windowTouched} times`} </div> ); } export default App;
内容的提问来源于stack exchange,提问作者lemme
相关产品推荐
相关产品推荐

