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> ); }
问题分析
- 状态分散导致竞态:每个Cell自己维护
possibleNum和num,组件间状态不同步。当父组件generatedNumbersMap更新时,Cell的useRef没有同步最新数据,校验用的是旧值。 - useEffect依赖不全:只依赖
row和col,generatedNumbersMap变化时不会触发重新计算,新单元格生成时旧单元格不会重新校验。 - 异步状态更新错误:
setPossibleNum是异步操作,循环里连续调用会覆盖更新;直接用当前possibleNum过滤,拿到的是旧状态,导致可用数值计算错误。 - 生成顺序混乱:所有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
相关产品推荐
相关产品推荐

