React战舰游戏项目中如何修改数组指定位置元素的classList更改样式
问题根源
- 你的
boxes数组存储的是0、1的数值,不是DOM元素,不存在classList属性,直接调用该属性修改类名必然无效 - React遵循数据驱动视图的渲染逻辑,你手动修改DOM的操作会在组件重渲染时被还原,不符合React的开发范式
- 判断条件
if (boxes === 0)永远不会成立,boxes是二维数组,不可能等于数字0,你的修改逻辑从一开始就没有执行
解决方法
改用状态管理网格数据和击中状态,通过修改状态来驱动类名自动更新,不需要手动操作DOM:
- 把网格数据和回合状态都存入useState,避免组件重渲染时数据被重置
- 新增二维数组状态存储每个格子的击中状态,AI射击时只需要修改这个状态的值
- 把AI射击逻辑放在useEffect中,监听回合状态触发射击操作
- 渲染格子的时候直接根据击中状态动态绑定className
修改后的完整代码
import React, { useEffect, useState } from "react"; import Ship from "../components/ShipGenerate.js"; import "../style/style.css"; function Grid(props) { // 初始化10*10的网格,1代表有船,0代表空 const initGrid = () => { const grid = new Array(10).fill(0).map(() => new Array(10).fill(0)); const rnd = (r) => Math.trunc(Math.random() * r); for (let l = 0; l < 40; l += 1) { grid[rnd(10)][rnd(10)] = 1; } return grid; }; // 初始化击中状态,0=未击中,1=击中船(绿),2=击中空(红) const initHitState = () => new Array(10).fill(0).map(() => new Array(10).fill(0)); const [grid, setGrid] = useState(initGrid); const [hitState, setHitState] = useState(initHitState); const [turn, setTurn] = useState(true); // true=玩家回合,false=AI回合 // AI射击逻辑,监听回合状态,AI回合时触发 useEffect(() => { if (!turn) { let x, y; // 选一个没被击中过的位置 do { x = Math.floor(Math.random() * 10); y = Math.floor(Math.random() * 10); } while (hitState[x][y] !== 0); // 更新击中状态 const newHitState = hitState.map(row => [...row]); newHitState[x][y] = grid[x][y] === 1 ? 1 : 2; setHitState(newHitState); // 射击完切换回玩家回合 setTurn(true); } }, [turn, hitState, grid]); const handleClick = (e, x, y) => { // 只有玩家回合才能点击 if (!turn || hitState[x][y] !== 0) return; // 更新玩家击中状态 const newHitState = hitState.map(row => [...row]); newHitState[x][y] = grid[x][y] === 1 ? 1 : 2; setHitState(newHitState); // 切换到AI回合 setTurn(false); }; return ( <div className="game-wrapper"> <div className="game-area"> {grid.map((row, x) => { return row.map((box, y) => { // 动态绑定类名 let className = "ship"; if (hitState[x][y] === 1) className = "green"; if (hitState[x][y] === 2) className = "red"; return ( <div key={`${x}-${y}`} onClick={(e) => handleClick(e, x, y)} className={className} > </div> ); }); })} </div> </div> ); } export default Grid;
补充说明
- 所有网格相关的数据都存在状态里,修改状态后React会自动更新对应DOM的类名,不需要你手动操作classList
- 加了重复射击的判断,避免AI反复打同一个位置
- 你原来写的
myShips查询DOM的逻辑完全可以删掉,React项目里除了特殊场景不要直接操作DOM
内容的提问来源于stack exchange,提问作者Chen
相关产品推荐
相关产品推荐

