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

React自定义Hook中isCalculating状态更新异常求助

自定义Hook中isCalculating状态无法实时更新问题的修复

问题描述

我编写了一个自定义Hook useTest,期望点击计算按钮后,isCalculating状态立即变为true以显示“计算中”提示,计算完成后更新result并将isCalculating设为false。但实际运行时,isCalculating的状态更新要等到for循环计算完成后才会触发,无法实时显示计算状态。

原始代码

useTest.ts

import { useEffect, useState } from 'react'

const TEST_LENGTH = 100 * 10000000
const TEST_CRITERIA = 99 * 10000000

const useTest = () => {
  const [isCalculating, setIsCalculating] = useState(false)
  const [result, setResult] = useState(0)
  useEffect(() => {
    setIsCalculating(false)
  }, [result])
  const calculate = () => {
    setIsCalculating(true)
    for (let i = 0; i < TEST_LENGTH; i++) {
      if (i >= TEST_CRITERIA) {
        setResult(i)
        break
      }
    }
  }
  const reset = () => {
    setResult(0)
  }
  return { isCalculating, result, calculate, reset }
}

export default useTest

Test.tsx

import { Box, Button, Container, Text } from '@chakra-ui/react'
import { useTest } from '../helpers'

export default function Test() {
  const { isCalculating, result, calculate, reset } = useTest()
  return (
    <Container>
      <Box>
        <Text>Result: {result}</Text>
        {isCalculating ? <Text>is calculating...</Text> : null}
        <Button
          onClick={() => {
            result === 0 ? calculate() : reset()
          }}>
          <Text>{result === 0 ? 'Calculate' : 'Reset'}</Text>
        </Button>
      </Box>
    </Container>
  )
}

已尝试的方法

我曾将计算逻辑移至监听isCalculating的useEffect中,但问题仍未解决:

import { useEffect, useState } from 'react'

const TEST_LENGTH = 100 * 10000000
const TEST_CRITERIA = 99 * 10000000

const useTest = () => {
  const [isCalculating, setIsCalculating] = useState(false)
  const [result, setResult] = useState(0)

  useEffect(() => {
    if (isCalculating) {
      for (let i = 0; i < TEST_LENGTH; i++) {
        if (i >= TEST_CRITERIA) {
          setResult(i)
          break
        }
      }
    }
  }, [isCalculating])
  const calculate = () => {
    setIsCalculating(true)
  }
  useEffect(() => {
    setIsCalculating(false)
  }, [result])
  const reset = () => {
    setResult(0)
  }
  return { isCalculating, result, calculate, reset }
}

export default useTest

问题根源

JavaScript是单线程模型,React的状态更新属于异步批量处理操作。同步的for循环会阻塞主线程,导致React无法及时处理状态更新和DOM渲染。即使调用了setIsCalculating(true),主线程被循环占用,直到循环结束后,React才会执行状态更新和组件重渲染,因此isCalculating的变化无法实时显示。

修复方案

方案1:使用setTimeout将计算放入宏任务队列

把耗时计算放到宏任务中,让主线程先处理React的状态更新和渲染:

import { useEffect, useState } from 'react'

const TEST_LENGTH = 100 * 10000000
const TEST_CRITERIA = 99 * 10000000

const useTest = () => {
  const [isCalculating, setIsCalculating] = useState(false)
  const [result, setResult] = useState(0)

  useEffect(() => {
    setIsCalculating(false)
  }, [result])

  const calculate = () => {
    setIsCalculating(true)
    // 将计算逻辑放入宏任务,让React优先处理状态更新
    setTimeout(() => {
      for (let i = 0; i < TEST_LENGTH; i++) {
        if (i >= TEST_CRITERIA) {
          setResult(i)
          break
        }
      }
    }, 0)
  }

  const reset = () => {
    setResult(0)
  }

  return { isCalculating, result, calculate, reset }
}

export default useTest

方案2:使用Web Worker(适合超大计算量)

如果计算量极大,Web Worker可以在独立线程中执行计算,完全不阻塞主线程:

  1. 创建calculation.worker.ts:
const TEST_LENGTH = 100 * 10000000
const TEST_CRITERIA = 99 * 10000000

self.onmessage = () => {
  for (let i = 0; i < TEST_LENGTH; i++) {
    if (i >= TEST_CRITERIA) {
      self.postMessage(i)
      break
    }
  }
}
  1. 修改useTest.ts:
import { useEffect, useState } from 'react'

const useTest = () => {
  const [isCalculating, setIsCalculating] = useState(false)
  const [result, setResult] = useState(0)
  let worker: Worker | null = null

  useEffect(() => {
    // 初始化Web Worker
    worker = new Worker(new URL('./calculation.worker.ts', import.meta.url))
    worker.onmessage = (e) => {
      setResult(e.data)
      setIsCalculating(false)
    }

    // 组件卸载时销毁Worker
    return () => worker?.terminate()
  }, [])

  const calculate = () => {
    setIsCalculating(true)
    worker?.postMessage('start')
  }

  const reset = () => {
    setResult(0)
    setIsCalculating(false)
  }

  return { isCalculating, result, calculate, reset }
}

export default useTest

方案说明

  • 方案1实现简单,适合中等计算量场景,仅需少量改动即可让状态更新实时生效;
  • 方案2适合超大计算量场景,彻底避免主线程阻塞,同时需要额外创建Worker文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 19:54:52