React中使用addEventListener和useState时如何修复闭包问题
问题根因
这是React Hooks场景下的典型闭包陷阱:
- 你的事件绑定逻辑写在依赖数组为空的
useEffect中,仅会在组件首次挂载时执行1次 - 首次执行时绑定的
handler函数,捕获的是首次渲染上下文中的num值(也就是初始值0) - 后续
num更新触发组件重渲染时,会生成新的handler函数引用,但空依赖的useEffect不会重新执行,window上绑定的始终是第一次渲染时生成的旧handler,持有的永远是初始的0值,所以控制台始终输出0。

修复方案
这里提供两种常用的修复方式,按需选择即可:
方案1:补全useEffect依赖,增加清理逻辑
将num加入useEffect的依赖数组,当num变化时自动解绑旧的事件监听、绑定持有最新num值的新监听,注意必须写清理逻辑避免内存泄漏。
import "./App.css"; import { useState, useEffect } from "react"; function App() { const [num, setNum] = useState(0); const handler = () => { console.log(num); }; useEffect(() => { window.addEventListener("click", handler); // 依赖更新/组件卸载时移除旧监听 return () => window.removeEventListener("click", handler); }, [num]); return ( <div className="App"> <header className="App-header"> <div onClick={() => { setNum(num + 1); }} > Add </div> <h1>{num}</h1> </header> </div> ); } export default App;
方案2:使用useRef存储最新值,避免重复绑定事件
如果不想每次num更新都重绑事件,可以用useRef存储最新的num值,ref对象的current属性始终指向最新值,不会受闭包影响。
import "./App.css"; import { useState, useEffect, useRef } from "react"; function App() { const [num, setNum] = useState(0); // 用ref同步存储最新的num值 const latestNum = useRef(num); useEffect(() => { latestNum.current = num; }, [num]); const handler = () => { // 从ref读取最新值 console.log(latestNum.current); }; useEffect(() => { window.addEventListener("click", handler); return () => window.removeEventListener("click", handler); }, []); // 仅在组件挂载时绑定一次即可 return ( <div className="App"> <header className="App-header"> <div onClick={() => { setNum(num + 1); }} > Add </div> <h1>{num}</h1> </header> </div> ); } export default App;
注意:两种方案都需要在useEffect中返回清理函数移除事件监听,否则组件卸载后监听仍然存在,会造成内存泄漏。
内容的提问来源于stack exchange,提问作者Dimchykus
相关产品推荐
相关产品推荐

