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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 12:30:58