反应钩子:新状态值未反映在 setInterval 回调中

Posted

技术标签:

【中文标题】反应钩子:新状态值未反映在 setInterval 回调中【英文标题】:React hooks: new state value not reflecting in setInterval callback 【发布时间】:2019-08-29 00:06:12 【问题描述】:

我有一个函数 react 组件,它有一个从 10000 开始到 0 的计数器。

我在组件安装期间使用 useEffect 挂钩设置 setInterval 回调。回调然后更新计数器状态。

但我不知道为什么,count 的值永远不会减少。每次回调运行count 为10000。

(我正在使用 react & react-dom 版本16.8.3

功能组件如下:

import React,  useState, useEffect, useRef  from 'react'

const Counter = () => 
  const timerID = useRef()
  let [count, setCount] = useState(10000)

  useEffect(() => 
    timerID.current = setInterval(() => 
      //count here is always 10000
      if (count - 1 < 0) 
        setCount(0)
       else 
        setCount(count - 1)
      
    , 1)
  , [])

  return <h1 className="counter">count</h1>


export default Counter

这里是codeandbox的链接:link

【问题讨论】:

setState() 是异步的... 不要认为这很重要。 setCount(--count ) 有效。可能是最好的方法,也可能不是?这是一个关闭问题 将计数设置为依赖项并使用 setTimeout 将解决您的痛苦:)。因为下次渲染时会在count有新值时再次调用setTimeout 【参考方案1】:

你需要注意count的变化,同时清理你的useEffect()

useEffect(() => 
    timerID.current = setInterval(() => 
      if (count - 1 < 0) 
        setCount(0)
       else 
        setCount(count - 1)
      
    , 100)

    return () => clearInterval(timerID.current);
  , [count])

正如@Pavel 提到的,Dan Abramov 解释了为什么here。

【讨论】:

嗨@Colin,每次更新计数时不会设置新的间隔吗?我不希望这种情况发生。 setInterval() 应该在挂载期间只设置一次。 会的。如果您查看博客文章,有一个建议的 useInterval 钩子可以做类似但更复杂的事情。 TLDR;您需要设置并清除每次重新渲染的时间间隔,但这不应该是一项昂贵的操作。 知道了!!想知道在 google 上搜索时我怎么找不到该博客文章...> 【参考方案2】:

如您所说,您在组件安装时声明效果功能。因此,在该时间值存储范围内,计数等于 10000。这意味着每次执行时间间隔函数都会从闭包(10000)中获取计数值。正确地做到这一点实际上非常困难。丹写了整个blog post

【讨论】:

知道了!!想知道在谷歌上搜索时我怎么找不到该博客文章...> 【参考方案3】:

有两种选择:

1) 在依赖项中包含count

这并不理想,因为这意味着每次更改 count 都会创建一个新的 setInterval,因此您需要在每次渲染时清理它,例如:

  useEffect(() => 
    timerID.current = setInterval(() => 
      //count here is always 10000
      if (count - 1 < 0) 
        setCount(0)
       else 
        setCount(count - 1)
      
    , 1)
    return () => clearInterval(timerID.current) // Added this line
  , [count]) // Added count here

2) 在setInterval回调函数中添加count

这是最好的间隔方法,因为它避免了一直设置新的间隔。

 useEffect(() => 
    timerID.current = setInterval(() => 
      // count is used inside the setCount callback and has latest value
      setCount(count => 
        if (count - 1 < 0)  // Logic moved inside the function, so no dependencies
          if (timerID.current) clearInterval(timerID.current)
          return 0
        
        return count - 1
      )
    , 1)
    return () => 
      if (timerID.current) clearInterval(timerID.current) // Makes sure that the interval is cleared on change or unmount
    
  , [])

这里是sandbox link

【讨论】:

以上是关于反应钩子:新状态值未反映在 setInterval 回调中的主要内容,如果未能解决你的问题,请参考以下文章

反应钩子。无法对未安装的组件执行 React 状态更新

使用上下文和钩子更新未安装组件的状态 - 反应原生

反应本机代码更改未反映在设备上

传递给子组件时道具未定义(反应钩子)

反应钩子:useState/context;无法读取未定义的属性“头像”/如何更新嵌套对象

如何使用/不使用 useEffect 钩子在 React JS 函数中获取更改的(新)状态值?