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

React实现类Wordle应用按回车时State未正确更新问题

问题场景

使用React开发类Wordle小游戏时,用字符数组state存储当前输入的字母,用数组state存储已提交的尝试内容,通过全局键盘事件监听输入:

  • 输入字母时,通过函数式更新累加currentAttempt数组,页面渲染正常
    输入正常运行截图
  • 按下回车键提交时,控制台打印的currentAttempt为空,最终存入storedAttempts的内容为空
    控制台空输出截图

问题相关核心代码如下:

import { useEffect, useState } from "react";

const Board = () => {
  const [currentAttempt, setCurrentAttempt] = useState([]);
  const [storedAttempts, setStoredAttempts] = useState([]);

  useEffect(() => {
    const onLetterTyped = (e) => {
      // Accept only letters
      if (e.keyCode >= 65 && e.keyCode <= 90) {
        setCurrentAttempt((prevState) => [...prevState, e.key]);
      }
      // Enter
      if (e.keyCode === 13) {
        console.log("Current attempt: " + currentAttempt);
        setStoredAttempts((prevState) => [
          ...prevState,
          currentAttempt.join(""),
        ]);
        setCurrentAttempt([]);
      }
    };
    window.addEventListener("keyup", onLetterTyped);
    return () => {
      window.removeEventListener("keyup", onLetterTyped);
    };
  }, []);

  return (
    <div>
      <p>{currentAttempt}</p>
      <div>
        {storedAttempts.map((word, i) => (
          <div key={i}>{word}</div>
        ))}
      </div>
    </div>
  );
};
export default Board;
问题原因

这是React Hooks使用时非常典型的**陈旧闭包(Stale Closure)**问题:

  • 你给useEffect传入的依赖数组是空数组[],代表这个副作用逻辑只会在组件首次挂载时执行一次,后续不会重新执行。
  • 首次执行时绑定的onLetterTyped键盘事件回调函数,闭包内捕获的currentAttempt永远是组件初次渲染时的初始值——也就是空数组[]。

输入字母时页面能正常渲染,是因为字母输入逻辑用了函数式更新:setCurrentAttempt((prevState) => [...prevState, e.key]),这种写法不需要依赖外部闭包捕获的currentAttempt值,React会自动传入最新的前序状态,所以能正常累加字符。
但按下回车的逻辑里,直接读取了闭包内捕获的currentAttempt,这个值从组件挂载开始就没有更新过,永远是空数组,所以控制台打印为空,拼接后存入storedAttempts的也会是空字符串。

修复方案

可以任选以下两种方案修复:

方案1:将状态加入useEffect依赖数组

把currentAttempt加入useEffect的依赖列表,每次currentAttempt变化时,就会重新执行副作用:解绑旧的事件回调,绑定能捕获到最新状态值的新回调。
修改后的useEffect代码如下:

useEffect(() => {
  const onLetterTyped = (e) => {
    // 仅接受字母输入
    if (e.keyCode >= 65 && e.keyCode <= 90) {
      setCurrentAttempt((prevState) => [...prevState, e.key]);
    }
    // 回车键提交逻辑
    if (e.keyCode === 13) {
      console.log("Current attempt: " + currentAttempt);
      setStoredAttempts((prevState) => [
        ...prevState,
        currentAttempt.join(""),
      ]);
      setCurrentAttempt([]);
    }
  };
  window.addEventListener("keyup", onLetterTyped);
  return () => {
    window.removeEventListener("keyup", onLetterTyped);
  };
}, [currentAttempt]); // 将currentAttempt加入依赖

这种写法符合React Hooks的常规设计逻辑,可读性高,适合绝大多数场景。

方案2:用Ref同步最新状态,避免事件重复绑定

如果不想每次输入字母都重新绑定键盘事件,可以借助useRef存储最新的状态值,让事件回调随时能读到最新的currentAttempt,不需要修改useEffect的空依赖:

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

const Board = () => {
  const [currentAttempt, setCurrentAttempt] = useState([]);
  const [storedAttempts, setStoredAttempts] = useState([]);
  // 用ref同步跟踪currentAttempt的最新值
  const currentAttemptRef = useRef(currentAttempt);
  currentAttemptRef.current = currentAttempt;

  useEffect(() => {
    const onLetterTyped = (e) => {
      if (e.keyCode >= 65 && e.keyCode <= 90) {
        setCurrentAttempt((prevState) => [...prevState, e.key]);
      }
      if (e.keyCode === 13) {
        const latestVal = currentAttemptRef.current;
        console.log("Current attempt: " + latestVal);
        setStoredAttempts((prevState) => [
          ...prevState,
          latestVal.join(""),
        ]);
        setCurrentAttempt([]);
      }
    };
    window.addEventListener("keyup", onLetterTyped);
    return () => {
      window.removeEventListener("keyup", onLetterTyped);
    };
  }, []); // 保持空依赖,事件只在挂载时绑定一次

  return (
    <div>
      <p>{currentAttempt}</p>
      <div>
        {storedAttempts.map((word, i) => (
          <div key={i}>{word}</div>
        ))}
      </div>
    </div>
  );
};
export default Board;

这种方案性能更好,适合事件绑定逻辑较重、状态更新频繁的场景。Wordle类游戏输入频率低,两种方案实际体验没有可感知的差异。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:31:01