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

React Wordle游戏切换难度后输入回滚初始状态问题求助

问题根源与解决方案

核心问题分析

  1. 闭包陷阱:handleKeyDown在组件首次渲染时绑定到document,函数内部捕获的是首次渲染时的gameBoard props。切换难度后,父组件传入新的gameBoard,但旧的事件监听依然引用旧的gameBoard实例,导致输入时回滚到初始状态。
  2. 直接修改原状态对象:handleKeyDown里直接修改gameBoard的letterBox属性(比如letterBox.letter = null),没有通过不可变更新生成新对象,违反React状态不可变原则,导致状态与UI不同步。
  3. 浅拷贝问题:getBoardCopy方法中boardCopy.letterBoxes = this.letterBoxes是浅拷贝,修改副本的letterBoxes会直接影响原对象,导致状态混乱。

具体修复步骤

1. 修复事件监听的闭包问题

给useEffect添加正确的依赖,并在卸载时清理监听,确保每次gameBoard变化时绑定新的事件处理函数(注意要把setGameBoard从父组件作为props传入子组件):

// 子组件接收setGameBoard props
const GameComponent = ({ gameBoard, setGameBoard }) => {
  const currentLetter = useRef(0)
  const currentWord = useRef(0)

  useEffect(() => {
    const handleKeyDown = (e: globalThis.KeyboardEvent) => {
      const inputLetter = e.key.toUpperCase()
      if(inputLetter === 'BACKSPACE'){
        if (currentLetter.current - 1 < 0) return
        // 先复制新的board实例,修改副本
        const nextBoard = gameBoard.getBoardCopy()
        const letterBox = nextBoard.getLetterBox(currentLetter.current - 1, currentWord.current)
        letterBox.letter = null
        currentLetter.current--
        setGameBoard(nextBoard)
      } else if (inputLetter !== 'ENTER' && isAllowedLetter(inputLetter)){
        if (currentLetter.current >= gameBoard.riddledWord.length) return
        const nextBoard = gameBoard.getBoardCopy()
        const letterBox = nextBoard.getLetterBox(currentLetter.current, currentWord.current)
        letterBox.letter = inputLetter
        currentLetter.current++
        setGameBoard(nextBoard)
      } else if (inputLetter === 'ENTER'){
        if (currentLetter.current !== gameBoard.riddledWord.length) return
        calculateResult(gameBoard.riddledWord)
        currentWord.current++
        currentLetter.current = 0
        // 生成新副本更新状态
        const nextBoard = gameBoard.getBoardCopy()
        setGameBoard(nextBoard)
      }
    }

    document.addEventListener('keydown', handleKeyDown)
    // 清理旧监听,避免内存泄漏
    return () => document.removeEventListener('keydown', handleKeyDown)
  }, [gameBoard, setGameBoard, currentLetter, currentWord]) // 添加所有依赖项

  // 渲染逻辑...
}

2. 修正getBoardCopy的深拷贝问题

把浅拷贝改成深拷贝,避免修改副本影响原对象:

getBoardCopy(){
  const boardCopy = new MainGameBoard(this.letterBoxQty)
  // 深拷贝letterBoxes,避免引用同一对象
  boardCopy.letterBoxes = JSON.parse(JSON.stringify(this.letterBoxes))
  // 数组做浅拷贝(如果是对象数组需深拷贝)
  boardCopy.words = [...this.words]
  // 字符串是值类型,直接赋值即可
  boardCopy.riddledWord = this.riddledWord
  return boardCopy
}

注意:如果letterBoxes包含函数或特殊对象,JSON.parse(JSON.stringify)会失效,此时需要手动实现递归深拷贝。

3. 移除子组件本地状态(若之前添加过)

不需要将gameBoard存在子组件本地状态,直接使用父组件传入的props,通过setGameBoard更新父组件状态即可。


额外注意点

  • 确保父组件将setGameBoard作为props传递给GameComponent,子组件才能触发状态更新。
  • currentLetter和currentWord用useRef跟踪输入位置是合理的,它们的更新不会触发组件重新渲染,符合场景需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 00:47:34