React Wordle游戏切换难度后输入回滚初始状态问题求助
问题根源与解决方案
核心问题分析
- 闭包陷阱:
handleKeyDown在组件首次渲染时绑定到document,函数内部捕获的是首次渲染时的gameBoardprops。切换难度后,父组件传入新的gameBoard,但旧的事件监听依然引用旧的gameBoard实例,导致输入时回滚到初始状态。 - 直接修改原状态对象:
handleKeyDown里直接修改gameBoard的letterBox属性(比如letterBox.letter = null),没有通过不可变更新生成新对象,违反React状态不可变原则,导致状态与UI不同步。 - 浅拷贝问题:
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
相关产品推荐
相关产品推荐

