如何让我的React Native组件响应计时器是否正在运行?

时间:2019-08-17 17:14:36

标签: react-native timer react-hooks

我无法在React Native组件中获取按钮以根据计时器是否正在运行来更改状态。我已将状态添加到我的组件中,以便在启动和停止计时器时进行切换,但是在停止计时器后进行切换时,计时器会继续运行(或永不停止)。神秘地(至少对于这个新手而言),如果我注释掉计时器停止时切换状态的那一行,计时器就可以正常工作。

也许我只是完全采用了错误的方法。

如何使组件中的元素响应我的计时器当前是否正在运行?


SillyCounter.tsx

import React, { useEffect, useState } from 'react'
import { View, Button, Text } from 'native-base'


const SillyCounter = () => {

    let timerID = 0
    const [ isRunning, updateIsRunning ] = useState(false)


    const startTimer = (): void => {
        clearInterval(timerID)
        timerID = setInterval(() => { /* ... */ }, 1000)
        updateIsRunning(true)
    }

    const stopTimer = (): void => {
        clearInterval(timerID)
        updateIsRunning(false) // Starts timer again! Comment out and timer stops as expected; why?
    }

    useEffect(() => {
        startTimer()
        return () => { stopTimer() }
    } )

    return (
        <View>
            <View style={ {flexDirection: "row"} }>
                <Button small transparent success onPress={ (): void => { startTimer() } }>
                    <Text> </Text>
                </Button>
                <Button small transparent danger onPress={ (): void => { stopTimer() } }>
                    <Text> </Text>
                </Button>
            </View>
            <Button small style={ Object.assign({},
                {backgroundColor: isRunning ? "red" : "green"} ) } >
                <Text>{isRunning ? "+" : "-"}</Text>
            </Button>
        </View>
    )

}

export default SillyCounter

1 个答案:

答案 0 :(得分:3)

这是由于useEffect造成的。

每次更改状态时,默认情况下都会执行useEffect

因此,当您单击按钮以stopTimer时,它会使用updateIsRunning(false)来更改状态,因为状态更改时,您的useEffect被执行,结果startTimer被执行,这将启动计时器再次。

注释updateIsRunning(false)时,状态不会改变,useEffect将不会执行。最后,您的计时器停止计时。

就您而言,您根本不需要useEffect。您可以删除它。但是如果您的计时器正在运行并且不小心导航到其他组件,则需要清理计时器,

useEffect(() => {
    return () => { clearInterval(timerID) }
}, [])

或者,如果您希望计时器在默认情况下首次安装组件时启动,则需要为useEffect提供第二个参数(空数组),

useEffect(() => {
    startTimer()

    return () => { 
      // Don't call `stopTimer()` here, it will give you the warning that cannot set state on unmounted component. 
      // The `clearInterval(timerID)` will do the work, because on component re-mount it will always starts with initial values.

      clearInterval(timerID) 
    }   
}, [])  //Now useEffect executes only once when component mounts and not for sub-sequent re-renders

要了解有关second argument的更多信息,请查看this中的Optimizing Performance by Skipping Effects部分。