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

React中无Stale State捕获按键:实体/虚拟键盘切换异常修复

实体键盘输入异常的原因与修复

异常原因

问题出在Keyboard组件的useEffect依赖项为空数组:组件挂载时绑定的keydown事件监听器,会始终引用组件初始化时的props.onClick函数。而初始的onClick函数捕获的是App组件中isKeyboardEnabled的初始值false,即便后续通过开关修改了isKeyboardEnabled的状态,事件监听器里的函数依然使用旧的闭包值,导致按下实体键盘时触发旧逻辑,输出“键盘未启用”日志。

修复方案

有两种可行的修复方式:

方式一:更新useEffect的依赖项

让Keyboard组件的useEffect监听props.onClick的变化,当onClick更新时重新绑定事件监听器,确保事件处理函数始终使用最新的状态值:

import React, { useState, useEffect } from "react";

export default function App(props) {
  const [isKeyboardEnabled, setIsKeyboardEnabled] = useState(false);
  const [str, setStr] = useState("");

  const onClick = (v) => {
    if (isKeyboardEnabled) {
      setStr((str) => str + v);
    } else {
      console.log(`keyboard not enabled ("${v}")`);
    }
  };

  return (
    <div>
      <div>str: "{str}"</div>
      <br />
      <Keyboard onClick={onClick} />
      <br />
      <button onClick={() => setIsKeyboardEnabled((v) => !v)}>
        isKeyboardEnabled: {isKeyboardEnabled.toString()}
      </button>
    </div>
  );
}

function Keyboard(props) {
  const handleKeyDown = (v) => {
    if (!/^[a-z]$/.test(v)) return;
    props.onClick(v);
  };
  useEffect(() => {
    const fn = (e) => handleKeyDown(e.key);
    document.addEventListener("keydown", fn);

    return () => {
      document.removeEventListener("keydown", fn);
    };
  }, [props.onClick]); // 添加上props.onClick作为依赖项
  return (
    <div>
      {[..."abcdefghijklmnopqrstuvwxyz"].map((v, i) => (
        <button key={i} onClick={() => handleKeyDown(v)}>{v}</button>
      ))}
    </div>
  );
}

方式二:将启用状态下移到Keyboard组件

把isKeyboardEnabled状态传递给Keyboard组件,在组件内部做启用判断,避免闭包捕获旧值的问题:

import React, { useState, useEffect } from "react";

export default function App(props) {
  const [isKeyboardEnabled, setIsKeyboardEnabled] = useState(false);
  const [str, setStr] = useState("");

  const addChar = (v) => {
    setStr((str) => str + v);
  };

  return (
    <div>
      <div>str: "{str}"</div>
      <br />
      <Keyboard 
        onCharPress={addChar} 
        isEnabled={isKeyboardEnabled} 
      />
      <br />
      <button onClick={() => setIsKeyboardEnabled((v) => !v)}>
        isKeyboardEnabled: {isKeyboardEnabled.toString()}
      </button>
    </div>
  );
}

function Keyboard({ onCharPress, isEnabled }) {
  const handleKeyDown = (v) => {
    if (!/^[a-z]$/.test(v)) return;
    if (isEnabled) {
      onCharPress(v);
    } else {
      console.log(`keyboard not enabled ("${v}")`);
    }
  };
  useEffect(() => {
    const fn = (e) => handleKeyDown(e.key);
    document.addEventListener("keydown", fn);

    return () => {
      document.removeEventListener("keydown", fn);
    };
  }, [isEnabled, onCharPress]); // 监听isEnabled和onCharPress的变化
  return (
    <div>
      {[..."abcdefghijklmnopqrstuvwxyz"].map((v, i) => (
        <button key={i} onClick={() => handleKeyDown(v)}>{v}</button>
      ))}
    </div>
  );
}

内容的提问来源于stack exchange,提问作者Nathan Chu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 08:15:03