React数独求解器:值变更时如何实现组件实时重新渲染?
实现React数独求解器的分步动画效果
问题核心在于同步执行的求解算法会一次性完成所有数值修改,React的setState会批量处理更新,导致只有最终结果才会触发组件渲染,无法看到分步变化。以下是具体修改方案:
关键修改点
- 添加延迟函数:通过
Promise封装setTimeout,让每一步修改后暂停,给浏览器留出渲染时间 - 异步化求解函数:将
autoSolveSudoku改为async函数,使用await等待状态更新和渲染完成 - 调整循环逻辑:将同步for循环改为异步友好的while循环,确保每一步修改后都能触发组件更新
修改后的完整代码
import { useEffect, useState } from 'react' import './SudokuSolver.css' export default function SudokuSolver() { // 0 stands for empty const sudoku = [8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 6, 0, 0, 0, 0, 0, 0, 7, 0, 0, 9, 0, 2, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 4, 5, 7, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 6, 8, 0, 0, 8, 5, 0, 0, 0, 1, 0, 0, 9, 0, 0, 0, 0, 4, 0, 0] const [sudokuObject, setSudokuObject] = useState(sudoku.map(tile => { return { value: tile, predetermined: tile !== 0 ? true : false } })) const sudokuSize = Math.sqrt(sudoku.length) const tileSizeInPixels = 70; // 新增延迟函数,控制每一步的间隔时间(单位:毫秒) const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)) const pixelsFromTop = (index) => { const subsectionBuffer = Math.floor((index / sudokuSize) / Math.sqrt(sudokuSize)) * 20 return ((Math.floor(index / sudokuSize)) * tileSizeInPixels) + subsectionBuffer } const pixelsFromLeft = (index) => { const subsectionBuffer = Math.floor((index % sudokuSize) / 3) * 20; return ((index % sudokuSize) * tileSizeInPixels) + subsectionBuffer } const isLegalByRow = (testNum, index, sudokuObject) => { const rowNum = Math.floor(index / sudokuSize) for(let i = 0; i < sudokuSize; i++){ if(sudokuObject[i + (rowNum * sudokuSize)].value === testNum) return false } return true } const isLegalByColumn = (testNum, index, sudokuObject) => { const columnNum = (index) % (sudokuSize) for(let i = 0; i < sudokuSize; i++){ if(sudokuObject[(i * sudokuSize) + columnNum].value === testNum) return false; } return true; } const isLegalBySubsection = (testNum, index, sudokuObject) => { const subsectionSize = Math.sqrt(sudokuSize) const rowNum = Math.floor(index / sudokuSize) const columnNum = index % sudokuSize const subsectionRow = Math.floor(rowNum / Math.sqrt(sudokuSize)) const subsectionColumn = Math.floor(columnNum / Math.sqrt(sudokuSize)) const subsectionRowSize = subsectionSize * sudokuSize const startingIndex = subsectionRowSize * subsectionRow + (subsectionColumn * subsectionSize) for(let i = 0; i < subsectionSize; i++){ for(let j = 0; j < subsectionSize; j++){ if(sudokuObject[(startingIndex + j) + i * sudokuSize].value === testNum) return false } } return true } const isLegal = (testNum, index, tempObject) => { return(isLegalBySubsection(testNum, index, tempObject) && isLegalByRow(testNum, index, tempObject) && isLegalByColumn(testNum, index, tempObject)) } // 修改为异步函数 const autoSolveSudoku = async () => { let goingForward = true; // 每次基于当前状态创建副本,避免直接修改原状态 let tempObject = [...sudokuObject]; let i = 0; // 改用while循环适配异步逻辑 while (i < tempObject.length && i >= 0) { if(tempObject[i].predetermined){ if(goingForward){ i++; continue; } else { i -= 1; continue; } } let foundValid = false; let startNum = tempObject[i].value === 0 ? 1 : tempObject[i].value + 1; for(let j = startNum; j <= sudokuSize; j++){ if(isLegal(j, i, tempObject)){ tempObject[i].value = j; goingForward = true; foundValid = true; // 更新状态并等待渲染 setSudokuObject([...tempObject]); await delay(100); // 可调整此值控制动画速度 break; } } if(!foundValid){ tempObject[i].value = 0; goingForward = false; // 更新状态并等待渲染 setSudokuObject([...tempObject]); await delay(100); } if(goingForward){ i++; } else { i--; } } } return <div style={{top:'50px', left:'50px', position:'relative'}}> {sudokuObject.map((tile, index) => { return <div key={index} id="tile" style={{top: pixelsFromTop(index), left: pixelsFromLeft(index), color: tile.predetermined && 'blueviolet'}}> {tile.value !== 0 ? tile.value : " "} </div> })} <button id='solvebutton' onClick={autoSolveSudoku}>Auto Solve</button> </div> }
额外优化说明
- 给每个tile元素添加
key={index},符合React列表渲染的最佳实践 - 可通过调整
delay(100)中的数值控制动画速度,数值越大,每一步间隔越长 - 每次更新状态时都创建数组的新副本(
[...tempObject]),确保React能检测到状态变化并触发渲染
内容的提问来源于stack exchange,提问作者Jordan Amberg
相关产品推荐
相关产品推荐

