React井字棋游戏Hook使用报错求助:TypeError问题排查
问题描述
我是React初学者,跟着教程用State和Hook实现井字棋(Morpion)游戏,但页面无法正常显示,当前报错:
Uncaught TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
错误位置在index.js:54:1。
原代码
function Game () { const [history,setHistory]= useState[{squares:Array(9).fill(null)}] const [xIsNext,setXIsNext]= useState('true') const [stepNumber,setStepNumber]= useState(0) const [current,setCurrent]=useState(null) const [winner,setWinner]=useState(null) const handleClick = (i) =>{ history = history.slice(setStepNumber(stepNumber +1)); current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]){ return; } squares[i]= xIsNext ? 'X':'O'; setHistory(history.concat([{squares:squares}])); setStepNumber(history.length); setXIsNext(!xIsNext); } const jumpTo = (step) => { return( setStepNumber(step) && setXIsNext(step % 2) === 0 )}; const moves = history.map((step,move) => { const desc= move ? 'revenir au tour numero'+ move: 'revenir au début de la partie'; return( <li key={move}> <button onClick={() => jumpTo(move)}> {desc} </button> </li> ); }); useEffect(() => { const history = history; const current = history[stepNumber]; setWinner(calculateWinner(current.squares)); }); let status = null; if (winner){ status = winner +'a gagné' } else { status = 'Prochain joueur:' + (xIsNext ? 'X':'O') } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={(i) => handleClick(i)}/> </div> <div className="game-info"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ); }
错误原因及修复方案
1. useState语法错误(报错根源)
useState[{squares:Array(9).fill(null)}] 缺少括号,应改为useState([{squares:Array(9).fill(null)}])。React的useState需要传入可迭代的初始值(这里是数组),缺少括号会导致解构赋值时触发"undefined不可迭代"的错误。
2. 直接修改State变量
React中禁止直接给state变量赋值(如history = ...、current = ...),state只能通过对应的setter函数更新。同时current无需单独作为state存储,直接通过history[stepNumber]计算即可。
3. 布尔值初始值错误
useState('true') 传入的是字符串类型,应改为useState(true),否则!xIsNext的逻辑判断会不符合预期。
4. jumpTo函数逻辑错误
setXIsNext(step % 2) === 0 括号位置错误,应改为setXIsNext(step % 2 === 0);同时setState是异步操作,不能用&&连接多个setState调用,需分开执行。
5. useEffect缺少依赖数组
未指定依赖数组会导致useEffect无限循环,需添加[history, stepNumber]作为依赖,确保仅当历史记录或步数变化时才重新计算赢家。
6. handleClick函数逻辑错误
history.slice(setStepNumber(stepNumber +1)) 逻辑混乱,正确写法是先截断历史到当前步数const newHistory = history.slice(0, stepNumber + 1);,再添加新步骤。
修复后的完整代码
import { useState, useEffect } from 'react'; // 补充教程中默认的calculateWinner函数 function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; } // 补充教程中默认的Board组件 function Board({ squares, onClick }) { const renderSquare = (i) => { return ( <button className="square" onClick={() => onClick(i)}> {squares[i]} </button> ); }; return ( <div> <div className="board-row"> {renderSquare(0)} {renderSquare(1)} {renderSquare(2)} </div> <div className="board-row"> {renderSquare(3)} {renderSquare(4)} {renderSquare(5)} </div> <div className="board-row"> {renderSquare(6)} {renderSquare(7)} {renderSquare(8)} </div> </div> ); } function Game () { const [history, setHistory] = useState([{squares: Array(9).fill(null)}]); const [xIsNext, setXIsNext] = useState(true); const [stepNumber, setStepNumber] = useState(0); const [winner, setWinner] = useState(null); // 直接计算当前步骤,无需单独维护state const current = history[stepNumber]; const handleClick = (i) => { // 截断历史到当前步数,避免跳步后保留旧历史分支 const newHistory = history.slice(0, stepNumber + 1); const currentStep = newHistory[newHistory.length - 1]; const squares = currentStep.squares.slice(); // 已有赢家或格子被占用时,终止操作 if (winner || squares[i]) { return; } squares[i] = xIsNext ? 'X' : 'O'; setHistory([...newHistory, { squares }]); setStepNumber(newHistory.length); setXIsNext(!xIsNext); }; const jumpTo = (step) => { setStepNumber(step); // 步数为偶数时,下一个玩家是X(从0开始计数) setXIsNext(step % 2 === 0); }; const moves = history.map((step, move) => { const desc = move ? `revenir au tour numero ${move}` : 'revenir au début de la partie'; return ( <li key={move}> <button onClick={() => jumpTo(move)}>{desc}</button> </li> ); }); useEffect(() => { setWinner(calculateWinner(current.squares)); }, [history, stepNumber]); let status = winner ? `${winner} a gagné` : `Prochain joueur: ${xIsNext ? 'X' : 'O'}`; return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={(i) => handleClick(i)} /> </div> <div className="game-info"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ); } export default Game;
额外说明
- 补充了教程中默认包含的
calculateWinner和Board组件,确保代码可直接运行 - 优化了变量命名和逻辑可读性,符合React状态管理最佳实践
内容的提问来源于stack exchange,提问作者emeraude

