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

React中for循环生成井字棋方块时钩子失效问题求助

井字棋Board组件循环改写后点击无响应问题解决

问题描述

跟随React井字棋教程的扩展任务,用双层for循环重写Board组件后,界面能正常渲染且key符合要求,但点击方块无法显示X/O标记,仅第一步操作会被记录,后续点击无响应。

原硬编码版本代码

function Board({ xIsNext, squares, onPlay }) {
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    onPlay(nextSquares);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = 'Winner: ' + winner;
  } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

改写后的版本代码

function Board({ xIsNext, squares, onPlay }) {

  function handleClick(i) {
    if (squares[i] || calculateWinner(squares)) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    }
    else {
      nextSquares[i] = "O";
    }
    onPlay(nextSquares);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = "Winner: " + winner;
  }
  else {
    status = "Next player: " + (xIsNext ? "X" : "O");
  }

  /* CHANGES START HERE */
  let content = [];
  let n=0;
  for (let i=0; i<3; i++) {
    let row = [];
    for (let j=0; j<3; j++) {
      row.push(<Square key={"square-" + n} value={squares[n]} onSquareClick={() => handleClick(n)} />);
      n++;
    }
    content.push(<div key={"row-" + i} className="board-row">{row}</div>);
  }

  return (
    <>
      <div className="status">{status}</div>
      {content}
    </>
  );
}

问题原因

循环中创建的事件回调存在闭包陷阱:外部声明的n变量被所有回调共享引用,当循环结束后n的值已经变为9,后续点击方块时,实际调用的是handleClick(9)。而squares数组只有9个元素(索引0-8),squares[9]不存在,会直接触发if (squares[i])的判断返回,导致无法执行后续的状态更新逻辑。

解决方案

方案1:直接计算索引(推荐)

去掉额外的n变量,通过i*3 + j直接计算每个方块的索引,确保每个回调绑定的是当前迭代的独立索引:

let content = [];
for (let i=0; i<3; i++) {
  let row = [];
  for (let j=0; j<3; j++) {
    const index = i * 3 + j; // 计算当前方块的索引
    row.push(<Square key={"square-" + index} value={squares[index]} onSquareClick={() => handleClick(index)} />);
  }
  content.push(<div key={"row-" + i} className="board-row">{row}</div>);
}

方案2:用let捕获当前循环值

在内部循环中用let声明变量保存当前n的值,避免共享引用:

let content = [];
let n=0;
for (let i=0; i<3; i++) {
  let row = [];
  for (let j=0; j<3; j++) {
    let currentN = n; // 用let捕获当前n的快照
    row.push(<Square key={"square-" + currentN} value={squares[currentN]} onSquareClick={() => handleClick(currentN)} />);
    n++;
  }
  content.push(<div key={"row-" + i} className="board-row">{row}</div>);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 04:46:33