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可以在独立线程中执行计算,完全不阻塞主线程:
- 创建
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 } } }
- 修改
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
相关产品推荐
相关产品推荐

