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

React生命游戏Canvas点击单元格变色失败,类型错误求助

问题分析与修复方案

核心问题梳理

  1. grid被错误定义为函数而非数组变量
    原代码中let grid = () => {...}把grid设为了生成网格的函数,而非存储当前网格状态的数组。在mouseClick里直接访问grid[gridX][gridY]时,实际是在尝试访问函数对象的属性,自然会出现类型错误。

  2. 坐标索引顺序颠倒

    • 网格创建时,grid的结构是grid[行索引][列索引](外层循环遍历rows,每行包含columns个元素)
    • 鼠标点击计算的gridX是列坐标(X轴对应列),gridY是行坐标(Y轴对应行),正确的访问方式应该是grid[gridY][gridX],而非grid[gridX][gridY]
  3. 渲染函数参数与变量名混淆
    原render函数的循环变量名col和row搞反了:grid.length是行数,外层循环应该遍历行索引,内层遍历列索引,同时fillRect的坐标对应关系需要保持一致。

  4. 点击事件中传递错误的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 21:38:10