React函数组件中事件处理器读取state值始终异常的问题
问题原因
这是典型的闭包陷阱导致的问题:
- 你的
useEffect依赖数组是空的,意味着它只会在组件首次渲染时执行一次,给document绑定了第一次渲染时创建的onClick函数。 - 这个初始的
onClick函数捕获了当时的isShown状态值(false),后续组件重新渲染时,新生成的onClick函数不会被绑定到document上,每次点击触发的都是旧的onClick,所以里面的isShown永远是初始的false,调用setIsShown(!isShown)只会一直把状态设为true,但你看到的console.log始终是false。 - 另外你还漏了清理事件监听,会导致内存泄漏。
修复方案
有两种常用的修复方式:
方案一:使用函数式更新(推荐)
利用setState的函数式更新特性,直接基于上一次的状态值进行修改,不需要依赖当前闭包中的isShown,同时加上监听清理:
import React, { useEffect, useState } from 'react'; export default function Popover({}: any) { const [isShown, setIsShown] = useState(false); useEffect(() => { function onClick(e: Event) { console.log(isShown); // 使用函数式更新,基于前一次状态取反 setIsShown(prevIsShown => !prevIsShown); } document.addEventListener('click', onClick); // 清理函数:组件卸载或依赖变化时移除监听 return () => { document.removeEventListener('click', onClick); }; }, []); // 依赖数组保持为空,因为函数式更新不依赖当前isShown return (<div></div>); }
方案二:添加依赖并清理旧监听
把onClick加入useEffect的依赖数组,每次isShown变化时重新绑定监听,但要先移除旧的监听:
import React, { useEffect, useState } from 'react'; export default function Popover({}: any) { const [isShown, setIsShown] = useState(false); function onClick(e: Event) { console.log(isShown); setIsShown(!isShown); } useEffect(() => { document.addEventListener('click', onClick); // 清理旧监听 return () => { document.removeEventListener('click', onClick); }; }, [onClick]); // 依赖onClick,每次onClick变化时重新绑定 return (<div></div>); }
不过这种方式每次组件渲染(isShown变化)都会重新绑定监听,相比方案一稍逊一些,更推荐方案一。
内容的提问来源于stack exchange,提问作者olivierr91
相关产品推荐
相关产品推荐

