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

React井字棋游戏Hook使用报错求助:TypeError问题排查

React井字棋游戏报错修复指南

问题描述

我是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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 21:36:21