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,错误的类型定义可能掩盖潜在问题。
解决方案
- 正确更新State数组:替换直接修改的方式,通过
setEmptyDraws创建新数组更新State:
const spliceNumber = (index: number) => { setEmptyDraws(prevEmptyDraws => prevEmptyDraws.filter(num => num !== index)); };
- 移除不必要的async/await:删除
handleClick和spliceNumber中的async和await关键字,修正为同步逻辑:
const handleClick = (indexOfDraw: number) => { if (checkWin() || drawers[indexOfDraw] !== "") return; spliceNumber(indexOfDraw); asMark(indexOfDraw); setComputerMove(!computerMove); };
- 修复电脑落子逻辑:给
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]);
- 修正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
相关产品推荐
相关产品推荐

