React中useState更新后的值无法在自定义事件监听函数中获取
问题根因
核心是React的闭包陷阱:
- 你编写的useEffect依赖为空数组,仅会在组件首次挂载时执行1次
- 事件绑定的
onMyEvent是首次渲染生成的函数,该函数闭包捕获的是首次渲染时的var1初始值[] - 后续var1更新触发组件重渲染时,会生成新的
onMyEvent函数,但没有解绑旧的事件监听也未绑定新函数,事件触发时始终执行首次渲染的旧函数,无法拿到更新后的var1值。
解决思路
方案1:补充useEffect依赖+清理函数
将var1和onMyEvent加入useEffect依赖数组,每次依赖变化时先解绑旧监听,再绑定新的监听,保证绑定的永远是最新的onMyEvent函数:
import { useEffect, useState, useCallback } from 'react'; function App() { const [var1, setVar1] = useState([]) // 配合useCallback减少不必要的函数重建 const onMyEvent = useCallback((e)=>{ console.log("Detail var1 in onMyEvent: ", var1) }, [var1]) useEffect(()=>{ document.addEventListener('my_event', onMyEvent); setTimeout(()=>setVar1(['a','b']),1000) // 组件卸载/依赖变化时解绑旧监听 return () => document.removeEventListener('my_event', onMyEvent); },[var1, onMyEvent]) // 补充依赖 const doSomething = ()=>{ document.dispatchEvent(new CustomEvent('my_event', { detail: 5 })); } return ( <div className="App"> <div>{var1.toString()}</div><br/> <button onClick={doSomething}>Show Data</button> </div> ); } export default App;
方案2:用useRef存储最新状态
useRef的current属性是可变引用,不会被闭包捕获旧值,也不需要调整事件绑定逻辑:
import { useEffect, useState, useRef } from 'react'; function App() { const [var1, setVar1] = useState([]) // 新建ref同步var1的最新值 const var1Ref = useRef(var1) // var1更新时同步刷新ref的值 useEffect(() => { var1Ref.current = var1 }, [var1]) const onMyEvent = (e)=>{ // 从ref读取最新值 console.log("Detail var1 in onMyEvent: ", var1Ref.current) } useEffect(()=>{ document.addEventListener('my_event', onMyEvent); setTimeout(()=>setVar1(['a','b']),1000) return () => document.removeEventListener('my_event', onMyEvent); },[]) const doSomething = ()=>{ document.dispatchEvent(new CustomEvent('my_event', { detail: 5 })); } return ( <div className="App"> <div>{var1.toString()}</div><br/> <button onClick={doSomething}>Show Data</button> </div> ); } export default App;
方案3:使用useEvent(React18+正式支持)
useEvent是官方专门为解决这类闭包问题提供的API,包装后的函数可以始终读取到最新的state,不需要额外添加依赖:
import { useEffect, useState, useEvent } from 'react'; function App() { const [var1, setVar1] = useState([]) // useEvent包装的回调永远能拿到最新state const onMyEvent = useEvent((e)=>{ console.log("Detail var1 in onMyEvent: ", var1) }) useEffect(()=>{ document.addEventListener('my_event', onMyEvent); setTimeout(()=>setVar1(['a','b']),1000) return () => document.removeEventListener('my_event', onMyEvent); },[]) const doSomething = ()=>{ document.dispatchEvent(new CustomEvent('my_event', { detail: 5 })); } return ( <div className="App"> <div>{var1.toString()}</div><br/> <button onClick={doSomething}>Show Data</button> </div> ); } export default App;
内容的提问来源于stack exchange,提问作者Ashish C
相关产品推荐
相关产品推荐

