React中Esc键触发close函数时modal state未同步更新的原因
问题:React中Esc键触发close函数打印旧state值的原因及解决
问题详情
在React应用中定义了modal状态,初始值为{show: false, photo: null}。页面包含两个按钮:
- 点击
open按钮会将modal设置为{show: true, photo: true} - 点击
close按钮会打印当前modal的值
同时监听了Esc键,按下时调用close函数。
代码如下:
function App() { const [modal, setModal] = useState({ show: false, photo: null }); // open func function open() { setModal({ show: true, photo: true }); } // close func function close() { console.log(modal); } // function for esc key press function escFunc(event) { if (event.key === `Escape`) { close(); } } useEffect(() => { document.addEventListener(`keydown`, escFunc, true); return () => { document.removeEventListener(`keydown`, escFunc, true); }; }, []); return ( <> <button onClick={open}>open</button> <br /> <button onClick={close}>close</button> </> ); }
现象:点击open后再点close按钮,能正常打印更新后的{show: true, photo: true};但按下Esc键时,却打印初始的{show: false, photo: null}。
原因
这是闭包的特性导致的:
- 你的
useEffect依赖数组是空的,只会在组件第一次挂载时执行一次。此时添加的事件监听器escFunc,捕获的是组件首次渲染时的modal值(初始状态)和当时的close函数。 - 当点击
open更新state后,组件会重新渲染,生成新的modal值和新的close函数。但因为useEffect没重新执行,旧的escFunc(以及它内部的旧close)依然绑定在事件上,所以按Esc时调用的是旧的close,自然打印旧的state。 - 而点击页面的
close按钮时,触发的是组件最新渲染生成的close函数,它捕获了最新的modal,所以能打印正确值。
解决办法
方法1:更新useEffect的依赖数组
把close函数加入useEffect的依赖数组,这样每次close更新(组件重新渲染生成新的close)时,useEffect会重新执行,移除旧监听器并添加新的,确保escFunc引用最新的close:
useEffect(() => { document.addEventListener(`keydown`, escFunc, true); return () => { document.removeEventListener(`keydown`, escFunc, true); }; }, [close]); // 添加close到依赖
方法2:用useRef保存最新state
创建一个ref对象,每次state更新时同步它的值,这样不管什么时候访问,都能拿到最新的state:
function App() { const [modal, setModal] = useState({ show: false, photo: null }); const modalRef = useRef(modal); // 同步state到ref useEffect(() => { modalRef.current = modal; }, [modal]); function open() { setModal({ show: true, photo: true }); } function close() { console.log(modalRef.current); // 从ref获取最新值 } function escFunc(event) { if (event.key === `Escape`) { close(); } } useEffect(() => { document.addEventListener(`keydown`, escFunc, true); return () => { document.removeEventListener(`keydown`, escFunc, true); }; }, []); return ( <> <button onClick={open}>open</button> <br /> <button onClick={close}>close</button> </> ); }
内容的提问来源于stack exchange,提问作者Pratik Dev
相关产品推荐
相关产品推荐

