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

React/Next.js数独开发:useEffect导致已生成单元格值异常变更

数独生成:3x3区块数值唯一性问题

我是React/Next.js新手,做数独项目练手,现在卡在3x3区块内数值无法保证唯一。用Map存单元格坐标和对应值,但渲染新单元格时,旧单元格的值会频繁变化,导致唯一性校验失效,我觉得唯一性逻辑本身是对的,但哪里出问题了?

主组件代码

export default function Sudoku() {
  const [generatedNumbersMap, setGeneratedNumbersMap] = useState(new Map());

  const addGeneratedNumber = (row: number, col: number, number: number) => {
    setGeneratedNumbersMap((prevMap) => {
      const newMap = new Map(prevMap);
      newMap.set(`${row},${col}`, number);
      return newMap;
    });
  };

  return (
    <main className="flex min-h-screen flex-col items-center justify-between">
      <div className="flex justify-center items-center h-screen">
        <div className="grid grid-rows-3 border-amber-400">
          {Array.from(Array(3)).map((_, blockRowIndex) => (
            <div key={blockRowIndex} className="flex">
              {Array.from(Array(3)).map((_, blockColIndex) => (
                <div
                  key={`${blockRowIndex}-${blockColIndex}`}
                  className="grid grid-rows-3"
                >
                  {Array.from(Array(3)).map((_, rowIndex) => (
                    <div
                      key={`${blockRowIndex}-${blockColIndex}-${rowIndex}`}
                      className="flex"
                    >
                      {Array.from(Array(3)).map((_, colIndex) => (
                        <Cell
                          key={`${blockRowIndex * 3 + rowIndex}-${
                            blockColIndex * 3 + colIndex
                          }`}
                          row={blockRowIndex * 3 + rowIndex}
                          col={blockColIndex * 3 + colIndex}
                          generatedNumbersMap={generatedNumbersMap}
                          addGeneratedNumber={addGeneratedNumber}
                        />
                      ))}
                    </div>
                  ))}
                </div>
              ))}
            </div>
          ))}
        </div>
      </div>
    </main>
  );
}

Cell组件代码

interface CellProps {
  row: number;
  col: number;
  generatedNumbersMap: Map<string, number>;
  addGeneratedNumber: (row: number, col: number, number: number) => void;
}

export default function Cell({
  row,
  col,
  generatedNumbersMap,
  addGeneratedNumber,
}: CellProps) {
  // Insert number into cell
  const [num, setNum] = useState(0);
  const [possibleNum, setPossibleNum] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9]);
  let revMap = useRef(generatedNumbersMap);
  useEffect(() => {
    let newNum: number = 0;
    const cornerRow: number = Math.floor(row / 3) * 3;
    const cornerCol: number = Math.floor(col / 3) * 3;
    for (let i = 0; i < 3; i++) {
      for (let j = 0; j < 3; j++) {
        if (cornerRow + i === row && cornerCol + j === col) {
          break;
        } else {
          const value = revMap.current.get(`${cornerRow + i}-${cornerCol + j}`);
          console.log(`${row}-${col}: ${value}`);
          if (value !== 0) {
            setPossibleNum(possibleNum.filter((item) => item !== value));
            const randomIndex = Math.floor(Math.random() * possibleNum.length);
            newNum = possibleNum[randomIndex];
          }
        }
      }
    }

    setNum(newNum);
    addGeneratedNumber(row, col, newNum);
  }, [row, col]);

  return (
    <div
      className={`border flex items-center justify-center text-lg h-10 w-10`}
    >
      {num}
    </div>
  );
}

问题分析

  1. 状态分散导致竞态:每个Cell自己维护possibleNum和num,组件间状态不同步。当父组件generatedNumbersMap更新时,Cell的useRef没有同步最新数据,校验用的是旧值。
  2. useEffect依赖不全:只依赖row和col,generatedNumbersMap变化时不会触发重新计算,新单元格生成时旧单元格不会重新校验。
  3. 异步状态更新错误:setPossibleNum是异步操作,循环里连续调用会覆盖更新;直接用当前possibleNum过滤,拿到的是旧状态,导致可用数值计算错误。
  4. 生成顺序混乱:所有Cell同时渲染并执行useEffect,互相更新状态导致旧单元格值被频繁覆盖。

解决方案

核心思路:把数独生成逻辑从Cell组件移到父组件,统一管控整个棋盘的生成,避免组件间状态冲突。

修改后的父组件(Sudoku)

import { useEffect, useState } from 'react';
import Cell from './Cell';

export default function Sudoku() {
  const [generatedNumbersMap, setGeneratedNumbersMap] = useState<Map<string, number>>(new Map());

  // 生成单个3x3区块的唯一随机数值
  const generateBlock = (blockRow: number, blockCol: number, targetMap: Map<string, number>) => {
    // 生成1-9的随机不重复数组
    const uniqueNums = Array.from({ length: 9 }, (_, i) => i + 1).sort(() => Math.random() - 0.5);
    const blockStartRow = blockRow * 3;
    const blockStartCol = blockCol * 3;

    // 填充区块内每个单元格
    uniqueNums.forEach((num, index) => {
      const row = blockStartRow + Math.floor(index / 3);
      const col = blockStartCol + index % 3;
      targetMap.set(`${row},${col}`, num);
    });
  };

  // 初始化生成所有区块
  useEffect(() => {
    const newMap = new Map<string, number>();
    // 遍历3x3个区块
    for (let blockRow = 0; blockRow < 3; blockRow++) {
      for (let blockCol = 0; blockCol < 3; blockCol++) {
        generateBlock(blockRow, blockCol, newMap);
      }
    }
    setGeneratedNumbersMap(newMap);
  }, []);

  return (
    <main className="flex min-h-screen flex-col items-center justify-between">
      <div className="flex justify-center items-center h-screen">
        <div className="grid grid-rows-3 border-amber-400">
          {Array.from(Array(3)).map((_, blockRowIndex) => (
            <div key={blockRowIndex} className="flex">
              {Array.from(Array(3)).map((_, blockColIndex) => (
                <div
                  key={`${blockRowIndex}-${blockColIndex}`}
                  className="grid grid-rows-3"
                >
                  {Array.from(Array(3)).map((_, rowIndex) => (
                    <div
                      key={`${blockRowIndex}-${blockColIndex}-${rowIndex}`}
                      className="flex"
                    >
                      {Array.from(Array(3)).map((_, colIndex) => {
                        const row = blockRowIndex * 3 + rowIndex;
                        const col = blockColIndex * 3 + colIndex;
                        return (
                          <Cell
                            key={`${row}-${col}`}
                            row={row}
                            col={col}
                            num={generatedNumbersMap.get(`${row},${col}`) || 0}
                          />
                        );
                      })}
                    </div>
                  ))}
                </div>
              ))}
            </div>
          ))}
        </div>
      </div>
    </main>
  );
}

简化后的Cell组件

interface CellProps {
  row: number;
  col: number;
  num: number;
}

export default function Cell({ row, col, num }: CellProps) {
  return (
    <div
      className="border border-gray-300 flex items-center justify-center text-lg h-10 w-10"
    >
      {num}
    </div>
  );
}

方案优势

  • 根源保证唯一性:每个区块直接生成1-9的随机不重复数组,从根本上避免3x3区块内数值重复。
  • 消除状态竞态:生成逻辑统一在父组件,Cell只负责渲染,组件间不再有状态冲突。
  • 代码更易维护:职责划分清晰,生成逻辑和渲染逻辑分离,后续扩展行/列唯一性校验也更方便。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 07:44:59