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

React井字棋开发问题:emptyDraws数组无法二次删除元素

井字棋emptyDraws数组删除元素失效问题

开发人机对战井字棋应用时遇到以下问题:首次操作可正常从emptyDraws数组中删除选中格子对应的元素,但电脑执行第二次操作时,无法将对应数字从emptyDraws数组移除,导致电脑后续会选中已填充的格子。

初始emptyDraws状态定义:

const [emptyDraws, setEmptyDraws] = useState<number[]>([
    0, 1, 2, 3, 4, 5, 6, 7, 8,
]);

期望效果:点击第三个格子(索引2)后移除该元素,电脑随机选中索引5后,emptyDraws最终变为:

const [emptyDraws, setEmptyDraws] = useState<number[]>([
    0, 1, 3, 4, 6, 7, 8, // 已移除数字2、5
]);

问题相关代码片段:

const [drawers, setDrawers] = useState<string[]>(Array(9).fill(""));
const [player, setPlayer] = useState<string>("x");

// 电脑是否允许落子
const [computerMove, setComputerMove] = useState<boolean | null>(false);

// 点击格子时执行的逻辑
const handleClick = async (indexOfDraw: number) => {
 if (checkWin() || drawers[indexOfDraw] !== "") return;

  await spliceNumber(indexOfDraw);
  asMark(indexOfDraw);
  setComputerMove(!computerMove);
};

// 可落子位置数组
const [emptyDraws, setEmptyDraws] = useState<number[]>([
 0, 1, 2, 3, 4, 5, 6, 7, 8,
]);

// 标记后移除对应数字
const spliceNumber = async (index: number) => {
 const indexer = await emptyDraws.indexOf(index);

 if (indexer > -1) {
   await emptyDraws.splice(index, 1);
 }
};

// 电脑落子逻辑
useEffect(() => {
 if (computerMove === false) return;
 let computerIndex;

 for (let i = 0; i < emptyDraws.length; i++) {
   const random = Math.floor(Math.random() * emptyDraws[i]);

   console.log("random: " + random);

   computerIndex = emptyDraws[random];
 }

 handleClick(computerIndex);
});

问题原因分析

  • 直接修改State数组:spliceNumber中使用emptyDraws.splice()直接修改了React State数组,React要求State必须通过setEmptyDraws更新,直接修改不会触发组件重新渲染,后续读取的始终是旧的State值。
  • 不必要的async/await:indexOf和splice都是同步操作,滥用async/await不会带来任何好处,反而可能导致逻辑时序混乱。
  • useEffect依赖缺失:电脑落子的useEffect没有设置依赖项,会在组件每次渲染时触发,可能导致重复调用handleClick;同时随机索引生成逻辑错误,Math.floor(Math.random() * emptyDraws[i])会生成超出数组范围的索引,导致computerIndex值异常。
  • State类型错误:emptyDraws的类型定义错误,应该是number[]而非number | any,错误的类型定义可能掩盖潜在问题。

解决方案

  1. 正确更新State数组:替换直接修改的方式,通过setEmptyDraws创建新数组更新State:
const spliceNumber = (index: number) => {
  setEmptyDraws(prevEmptyDraws => prevEmptyDraws.filter(num => num !== index));
};
  1. 移除不必要的async/await:删除handleClick和spliceNumber中的async和await关键字,修正为同步逻辑:
const handleClick = (indexOfDraw: number) => {
 if (checkWin() || drawers[indexOfDraw] !== "") return;

 spliceNumber(indexOfDraw);
 asMark(indexOfDraw);
 setComputerMove(!computerMove);
};
  1. 修复电脑落子逻辑:给useEffect添加正确的依赖项,修正随机索引生成逻辑:
useEffect(() => {
  if (computerMove !== true || emptyDraws.length === 0) return;
  
  const randomIndex = Math.floor(Math.random() * emptyDraws.length);
  const computerIndex = emptyDraws[randomIndex];
  handleClick(computerIndex);
}, [computerMove, emptyDraws]);
  1. 修正State类型定义:确保所有State的类型准确,避免any类型滥用:
const [emptyDraws, setEmptyDraws] = useState<number[]>([0, 1, 2, 3, 4, 5, 6, 7, 8]);
const [drawers, setDrawers] = useState<string[]>(Array(9).fill(""));
const [player, setPlayer] = useState<string>("x");

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:35:43