You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:33:15