我想在setinterval()中每秒更新一次状态,但它不起作用。我是react钩子的新手,所以不明白为什么会发生这种情况。请看下面的代码片段,并给予我建议。
// State definition
const [gamePlayTime, setGamePlayTime] = React.useState(100);
let targetShowTime = 3;
.........................
// call function
React.useEffect(() => {
gameStart();
}, []);
.............
const gameStart = () => {
gameStartInternal = setInterval(() => {
console.log(gamePlayTime); //always prints 100
if (gamePlayTime % targetShowTime === 0) {
//can not get inside here
const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
const targetPosition = { x: random, y: hp("90") };
const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
NewSpinShow(targetPosition, spinInfoData, spinSpeed);
}
setGamePlayTime(gamePlayTime - 1);
}, 1000);
};
4条答案
按热度按时间xzlaal3s1#
没有得到更新状态的原因是因为在useEffect(()=〉{},[])中调用了它,而该函数只被调用一次。
如果您的应用程序是一个组件,那么它的工作原理就像componentDidMount()一样。
当调用gameStart函数时,gamePlaytime为100,在gameStart内部,无论计时器如何工作,实际的gamePlayTime都是变化的,使用相同的值。在这种情况下,您应该使用useEffect监控gamePlayTime的变化。
8aqjt8rx2#
您正在创建一个closure,因为
gameStart()
在useEffect钩子运行时“捕获”了gamePlayTime
的值一次,此后不再更新。要解决这个问题,你必须使用React钩子状态更新的函数更新模式,而不是直接传递一个新值给
setGamePlayTime()
,你传递给它一个 function,这个函数在执行时接收旧的状态值,并返回一个新值来更新。例如:尝试这样做(实际上只是用功能状态更新来 Package setInterval函数的内容):
tjvv9vkg3#
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
Dan Abramov的文章很好地解释了如何使用钩子、状态和API的setInterval()类型!
丹·阿布拉莫夫!是React维护团队之一!非常出名,我个人很喜欢他!
快速解释
问题是如何使用只执行一次(第一次渲染)的
useEffect()
访问状态的问题!注意:对于为什么状态在
useEffect callback
和其他inside useEffect callbacks
中没有更新的深入解释,请查看最后一节关于闭包和react re-render...**简短的回答是:**通过使用refs(useRef)!和另一个useEffect(),当需要更新时再次运行!或者在每次渲染时!
让我来解释一下!看看丹·阿布拉莫夫的解决方案!你会更好地理解上面的语句!第二个例子不是关于setInterval()的!
=〉
**useEffect()**要么只运行一次,要么在每个渲染中运行!要么在依赖项更新时运行(如果提供)!
访问状态只能通过一个**useEffect()**在每个相关时间运行和渲染!
或者通过
setState((state/*here the state*/) => <newStateExpression>)
但是如果你想访问useEffect()=〉中的状态,就必须重新运行!这意味着传递并执行新的回调!
这不适合setInterval!如果你每次都清除它并重新设置它!计数器就会被重置!如果组件重新渲染得很快,就会导致不执行!这毫无意义!
如果你只渲染一次!状态不会更新!作为第一次运行,运行一个回调!并进行关闭!状态是固定的!
useEffect(() => { <run once, state will stay the same> setInterval(() => { <state fixed as closure of that time> }) }, [])
。对于所有这样的情况!我们需要使用
useRef
!(参)!保存一个保存状态的回调函数!从一个每次都重新呈现的
useEffect()
!或者保存状态值本身到ref
!取决于用法!丹·阿布拉莫夫解决方案为setInterval(简单而干净)
这就是你要找的!
useInterval挂钩(作者:Dan Abramov)
用法
我们可以看到他是如何在每次重新渲染时保存新的回调的!一个包含新状态的回调!
使用!这是一个干净简单的挂钩!这是一个美丽的!
一定要读丹的文章!因为他解释和处理了很多事情!
设置状态()
丹·阿布拉莫夫在他的文章中提到了这一点!
如果我们需要在setInteral!中设置state!,可以简单地使用setState()和回调版本!
我们甚至可以使用它!2即使我们没有设置状态!3可能!4但不好!5我们只是返回相同的状态值!
然而,这将使不同的React内部代码部分运行(1,2,3),并检查!这结束了保释从重新渲染!只是有趣的知道!
我们只在更新状态时使用这个!如果没有!那么我们需要使用引用!
另一个示例:带有getter版本的useState()
为了展示
how to work with refs and state access
!让我们看另一个例子!这里是另一个模式!在回调中传递状态!这个例子是同一类的!但在这里没有效果!
然而,我们可以使用上面的钩子来访问钩子中的状态,如下所示!
对于
setInterval()
!好的解决方案是丹阿布拉莫夫挂钩!使一个强大的自定义挂钩的东西是一件很酷的事情!这第二个例子是更多地展示了使用和重要性的参考,在这样的状态下访问需要或问题!这很简单!我们总是可以做一个自定义钩子!使用refs!并在
ref
中更新状态!或者一个保存新状态的回调!取决于用法!我们在render上设置ref(直接在自定义钩子[在render()中执行的块]中)!或者在useEffect()
中!在每次render时或根据依赖关系重新运行!关于useEffect()和refs设置的说明
关于**useEffect()**的注意事项
useEffect =〉useEffect异步运行,并且在屏幕上绘制渲染之后运行。
useEffect
次非常重要的一件事!
useEffect()
在render()完成后运行,屏幕在视觉上更新!它最后运行!您应该注意!一般来说,但是!效果应该在useEffect上运行()!所以任何自定义钩子都可以!因为它的
useEffect()
将在绘制之后和任何其他在render useEffect之前运行()!如果不是!就像需要直接在render中运行一些东西一样!那么你应该直接传递状态!有些人可能会传递回调!想象一下一些逻辑组件!并且传递了一个getState回调!这不是一个好的做法!如果你在某个地方做了一些这样有意义的事情!并且谈论
ref
!确保refs
是正确更新的!并且在此之前!但一般来说,你永远不会有问题!如果你这样做,那么它是一个气味!你试图去的方式是高可能不是正确的好方式!
闭包等等。为什么状态变量没有最新的值?
不能直接访问状态值的原因可以归结为
closure notion
.Render function at every re-render go totally with a new call
。每个调用都有它的闭包。在每次重新呈现时,useEffect callback
是一个新的匿名函数。有它的新取值范围。这里
dependency array
很重要。到access the closure of this call
。所以这个调用的最近状态。你必须让useEffect
使用new callback
。如果依赖关系改变了,那么就会发生。否则就不会发生。如果你运行
[]
,那么useEffect()
只会在第一次渲染后的每个新调用中运行。总是让useEffect
得到一个new anonymous function
。但是它们都没有生效或使用(运行)。同样的概念也适用于
useCallback
和许多other hooks
。而所有这些都是closures
的结果。useEffect回调内的回调
例如:
event listners
、setInterval
、setTimeout
、some(() => {})
、api.run(() => {})
现在即使你通过
dependency change
更新useEffect callback
。假设你做了一些event listener
或setInterval call
。但是你有条件地这样做,如果已经运行了,就不要再设置它了。setInterval callback
或event listener callback
不会访问最近的状态值。**为什么?**你已经猜到了。它们是created in the first run
,first call space and all the state values are closure passed down at that **time**
。在以后的更新和调用中。它是一个新的render function call
。也是一个完全不同的useEffect callback
。当然是it's the same function code
。但是not same functions at all
。The initial callback of the initial run on that render function call. Is a function that was created back then. And then the event listener callback or the setInterval callback was created back then
。它们是still in memory
和referred to
。事件侦听器API对象示例的事件侦听器部分,和使注册和setInterval成为节点运行时的一部分的对象。它们具有值为time.And never get to see or know about any other re-render call
.**Unless somehow you. inject something within. That
的状态闭包。or
仍然引用or
可以访问创建时间的最新值(references or globals). And those values come from the hooks (useState) and their internal machinery. All it would have is the
闭包.**
有趣的比喻
大多数人都会陷入这个陷阱。看看代码,问为什么状态更新时,它没有得到更新的值。答案是。状态值来自useState,它不是一个全局变量。即使你看到的是相同的代码。第一次调用和后面的调用都是不同的空格(beings)。在这个意义上,唯一使我们有可能使用函数的是钩子。以及它们如何存储状态并将其带回来。
**一个很好的比喻是:**去一个办公室,做一些合同和交易。然后回来,进入同一个办公室,但等待不是真的同一个办公室,而是一个看起来一样的办公室。但一个新的办公室取代了它的位置(搬出去,搬进来,同样的活动)。你想知道为什么他们没有认出你。是的,有点偏离比喻。但仍然很好一点。
总的来说,我希望这给了你一个很好的感觉!
f0ofjuux4#
不应该将setInterval与钩子一起使用。看看React.js的维护者之一Dan Abramov在他的博客上对另一种方法的评论:https://overreacted.io/making-setinterval-declarative-with-react-hooks/