如何清除在我的React组件中使用的计时器?

时间:2019-08-18 16:26:18

标签: reactjs react-native timer react-hooks

当我尝试从控件中重置计数器时,我很难弄清楚为什么我的计数器不会重置。我怀疑我在如何从控件内部操纵状态方面犯了一些新手(常见且不尴尬)的错误。

例如,如果我多次单击“ Faster”(快速),然后单击“ Normal”(正常),则计数将以加速的速度继续进行:显然,startTimer的调用并未清除较快的计时器。只有随后单击“重置”或“停止”后再单击“开始”,才能清除较快的计时器。但是,为什么要这样呢?我感到困惑:所有路径都以相同的方式使用clearInterval

我怀疑我没有掌握有关如何在组件中操纵状态的一般知识;或者也许如何从组件状态正确访问计时器。

为什么我的计时器无法按预期清除?

WobblyCounter.tsx

import React, { useState } from 'react'
import { View, Button, Text } from 'native-base'
import { useDispatch, useSelector } from 'react-redux'

const WobblyCounter = () => {

    const [ timerID, setTimerID ] = useState(0)
    const [ isRunning, updateIsRunning ] = useState(false)
    const [ interval, updateInterval ] = useState(1000)

    const count = useSelector((state) => state.count)
    const dispatch = useDispatch()

    const startTimer = (): void => {
        clearInterval(timerID)
        setTimerID(setInterval(() => { dispatch( {type: "INCREMENT", step: 1} ) }, interval))
        updateIsRunning(true)
    }

    const stopTimer = (): void => {
        clearInterval(timerID)
        updateIsRunning(false)
    }

    return (
        <View style={ {paddingTop:50} }>
            <Button
                onPress={ (): void => { dispatch( {type: "RESET"} ); startTimer() } }>
                <Text>Reset</Text>
            </Button>
            <View style={ {flexDirection: "row"} }>
                <Button small bordered dark disabled={ interval <= 250 }
                    onPress={ (): void => { updateInterval(Math.max(interval - 250, 250)); startTimer() } }>
                    <Text>Faster</Text>
                </Button>
                <Button small bordered dark disabled={ interval == 1000 }
                    onPress={ (): void => { updateInterval(1000); startTimer() } }>
                    <Text>Normal</Text>
                </Button>
                <Button small bordered dark
                    onPress={ (): void => { updateInterval(interval + 250); startTimer() } }>
                    <Text>Slower</Text>
                </Button>
            </View>
            <Button small style={ Object.assign( {}, {backgroundColor: isRunning ? "red" : "green"} ) }
                onPress={ (): void => { isRunning ? stopTimer() : startTimer() } }>
                <Text>{isRunning ? "Stop" : "Start"}</Text>
            </Button>
            <Text>
                Debug{"\n"}count = {count}{"\n"}interval = {interval}{"\n"}timerID = {timerID}
            </Text>
        </View>
    )

}

export default WobblyCounter

2 个答案:

答案 0 :(得分:1)

这里的主要问题是闭包startTimer使用的是旧状态值:

  1. 在第一个渲染timerID = 0和interval = 1000上,使用这些值创建startTimer
  2. 当您单击“ Faster”时,将调用updateInterval并将间隔状态更改为750,但该组件尚未渲染,因此使用旧值interval =调用startTimer 1000。
  3. 使用这些值重新创建组件重新渲染timerID = 1和interval = 750,startTimer
  4. 当您单击“普通”时,将调用updateInterval并将间隔状态更改为1000,但尚未渲染该组件,将以旧值interval = 750调用startTimer。这就是为什么计数器仍在快速运行的原因。

解决此问题的一种方法是使用自定义钩子useInterval proposed here by Dan Abramov,并且仅在单击按钮时更新相关状态(间隔,isRunning)。

  useInterval(
     () => {
         dispatch({ type: "INCREMENT", step: 1 });
     },
     isRunning ? interval : null
  );

您可以找到完整的代码here(我删除了react-native)

答案 1 :(得分:0)

您通过cleanup callback通过useEffect的{​​{3}}重置了计时器。

这意味着该副作用将在组件上运行一次,卸载:

useEffect(() => {
  // startTimer will run once on component mount
  startTimer();

  // The cleanup callback will run once on component unmount
  return stopTimer;
}, []);

但是,就您而言,您永远不会卸载组件(因为在单击按钮时分派操作,这意味着您始终处于更新周期),请尝试在stopTimer处设置一个断点。