React生命游戏Canvas点击单元格变色失败,类型错误求助
问题分析与修复方案
核心问题梳理
grid被错误定义为函数而非数组变量
原代码中let grid = () => {...}把grid设为了生成网格的函数,而非存储当前网格状态的数组。在mouseClick里直接访问grid[gridX][gridY]时,实际是在尝试访问函数对象的属性,自然会出现类型错误。坐标索引顺序颠倒
- 网格创建时,
grid的结构是grid[行索引][列索引](外层循环遍历rows,每行包含columns个元素) - 鼠标点击计算的
gridX是列坐标(X轴对应列),gridY是行坐标(Y轴对应行),正确的访问方式应该是grid[gridY][gridX],而非grid[gridX][gridY]
- 网格创建时,
渲染函数参数与变量名混淆
原render函数的循环变量名col和row搞反了:grid.length是行数,外层循环应该遍历行索引,内层遍历列索引,同时fillRect的坐标对应关系需要保持一致。点击事件中传递错误的render参数
mouseClick里调用render(grid)时,传递的是函数本身,而非当前的网格数组,导致渲染失败。
修复后的完整代码
import { useRef, useEffect } from "react"; export default function GameOfLifeCanvas() { const canvasRef = useRef(null); const resolution = 20; const width = 400; const height = 400; const cellTypes = [ { name: "standardCell", liveRepresentation: 1, color: "", behavior: {}, }, ]; const cellTypeCount = cellTypes.length + 1; useEffect(() => { const columns = width / resolution; const rows = height / resolution; const canvas = canvasRef.current; const context = canvas.getContext("2d"); // 抽离网格生成逻辑为独立函数 const createGrid = () => { const grd = []; for (let i = 0; i < rows; i++) { const row = []; for (let ii = 0; ii < columns; ii++) { row.push(Math.floor(Math.random() * cellTypeCount)); } grd.push(row); } return grd; }; // 初始化网格数组,保存当前状态 let grid = createGrid(); function mouseClick(e) { let mouseX = e.offsetX ?? e.layerX; let mouseY = e.offsetY ?? e.layerY; const gridX = Math.floor(mouseX / resolution); // 列索引 const gridY = Math.floor(mouseY / resolution); // 行索引 // 修正坐标访问顺序:grid[行][列] const currentValue = grid[gridY][gridX]; grid[gridY][gridX] = currentValue === 0 ? 1 : 0; render(grid); } canvas.addEventListener("mousedown", mouseClick, false); function render(grid) { // 清空画布避免渲染重叠 context.clearRect(0, 0, width, height); // 外层遍历行,内层遍历列,明确变量语义 for (let row = 0; row < grid.length; row++) { for (let col = 0; col < grid[row].length; col++) { const cell = grid[row][col]; context.fillStyle = cell ? "black" : "white"; context.fillRect( col * resolution, row * resolution, resolution, resolution ); context.stroke(); } } } // 初始渲染网格 render(grid); // 组件卸载时清理事件监听器 return () => { canvas.removeEventListener("mousedown", mouseClick); }; }, []); return ( <canvas ref={canvasRef} width={width} height={height}></canvas> ); }
关键修复点说明
- 把网格生成逻辑抽离为
createGrid函数,用grid变量保存当前网格状态,避免函数与数据变量混淆。 - 修正点击事件中的坐标访问顺序,确保
grid[gridY][gridX]对应正确的单元格。 - 调整
render函数的循环变量名,明确行和列的遍历顺序,同时添加clearRect避免渲染重叠。 - 添加组件卸载时的事件监听器清理,符合React组件生命周期最佳实践。
- 使用空值合并运算符
??简化鼠标坐标的获取逻辑。
内容的提问来源于stack exchange,提问作者Cal
相关产品推荐
相关产品推荐

