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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 02:45:32