reactjs 无法更新react挂接中setInterval内的状态

2exbekwf  于 2022-12-12  发布在  React
关注(0)|答案(4)|浏览(298)

我想在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);
  };
xzlaal3s

xzlaal3s1#

没有得到更新状态的原因是因为在useEffect(()=〉{},[])中调用了它,而该函数只被调用一次。
如果您的应用程序是一个组件,那么它的工作原理就像componentDidMount()一样。
当调用gameStart函数时,gamePlaytime为100,在gameStart内部,无论计时器如何工作,实际的gamePlayTime都是变化的,使用相同的值。在这种情况下,您应该使用useEffect监控gamePlayTime的变化。

...
  useEffect(() => {
      if (gamePlayTime % targetShowTime === 0) {
        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);
      }
  }, [gamePlayTime]);

  const gameStart = () => {
    gameStartInternal = setInterval(() => {
      setGamePlayTime(t => t-1);
    }, 1000);
  };
...
8aqjt8rx

8aqjt8rx2#

您正在创建一个closure,因为gameStart()在useEffect钩子运行时“捕获”了gamePlayTime的值一次,此后不再更新。
要解决这个问题,你必须使用React钩子状态更新的函数更新模式,而不是直接传递一个新值给setGamePlayTime(),你传递给它一个 function,这个函数在执行时接收旧的状态值,并返回一个新值来更新。例如:

setGamePlayTime((oldValue) => {
  const someNewValue = oldValue + 1;
  return someNewValue;
});

尝试这样做(实际上只是用功能状态更新来 Package setInterval函数的内容):

const [gamePlayTime, setGamePlayTime] = React.useState(100);
let targetShowTime = 3;

// call function
React.useEffect(() => {
    gameStart();
  }, []);

const gameStart = () => {
    gameStartInternal = setInterval(() => {
      setGamePlayTime((oldGamePlayTime) => {
        console.log(oldGamePlayTime); // will print previous gamePlayTime value
        if (oldGamePlayTime % targetShowTime === 0) {
          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);
        }
        return oldGamePlayTime - 1;
      });
    }, 1000);
  };
tjvv9vkg

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)

import React, { useState, useEffect, useRef } from 'react';

function useInterval(callback, delay) {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

用法

import React, { useState, useEffect, useRef } from 'react';

function Counter() {
  let [count, setCount] = useState(0);

  useInterval(() => {
    // Your custom logic here
    setCount(count + 1);
  }, 1000);

  return <h1>{count}</h1>;
}

我们可以看到他是如何在每次重新渲染时保存新的回调的!一个包含新状态的回调!
使用!这是一个干净简单的挂钩!这是一个美丽的!
一定要读丹的文章!因为他解释和处理了很多事情!

设置状态()

丹·阿布拉莫夫在他的文章中提到了这一点!
如果我们需要在setInteral!中设置state!,可以简单地使用setState()和回调版本!

useState(() => {
   setInterval(() => {
      setState((state/*we have the latest state*/) => {
          // read and use state
          return <newStateExpression>;
      })
   }, 1000);
}, []) // run only once

我们甚至可以使用它!2即使我们没有设置状态!3可能!4但不好!5我们只是返回相同的状态值!

setState((state) => {
   // Run right away!
   // access latest state
   return state; // same value (state didn't change)
});

然而,这将使不同的React内部代码部分运行(1,2,3),并检查!这结束了保释从重新渲染!只是有趣的知道!
我们只在更新状态时使用这个!如果没有!那么我们需要使用引用!

另一个示例:带有getter版本的useState()

为了展示how to work with refs and state access!让我们看另一个例子!这里是另一个模式!在回调中传递状态!

import React from 'react';

function useState(defaultVal) {
    // getting the state
    const [state, setState] = React.useState(defaultValue);
    // state holding ref
    const stateRef = React.useRef();
    stateRef.current = state; // setting directly here!
    // Because we need to return things at the end of the hook execution
    // not an effect

    // getter
    function getState() {
       // returning the ref (not the state directly) 
       // So getter can be used any where!
       return stateRef.current;
    } 
    
    return [state, setState, getState];
}

这个例子是同一类的!但在这里没有效果!
然而,我们可以使用上面的钩子来访问钩子中的状态,如下所示!

const [state, useState, getState] = useState(); // Our version! not react
// ref is already updated by this call

React.useEffect(() => {
  setInteval(() => {
      const state = getState();
      // do what you want with the state!
      // it works because of the ref! Get State to return a value to the same ref!
     // which is already updated 
  }, 1000)
}, []); // running only once

对于setInterval()!好的解决方案是丹阿布拉莫夫挂钩!使一个强大的自定义挂钩的东西是一件很酷的事情!这第二个例子是更多地展示了使用和重要性的参考,在这样的状态下访问需要或问题!

这很简单!我们总是可以做一个自定义钩子!使用refs!并在ref中更新状态!或者一个保存新状态的回调!取决于用法!我们在render上设置ref(直接在自定义钩子[在render()中执行的块]中)!或者在useEffect()中!在每次render时或根据依赖关系重新运行!

关于useEffect()和refs设置的说明

关于**useEffect()**的注意事项
useEffect =〉useEffect异步运行,并且在屏幕上绘制渲染之后运行。

  • 您以某种方式导致渲染(更改状态或父级重新渲染)
  • React呈现组件(调用它)
  • 屏幕在视觉上更新
  • 则运行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 listnerssetIntervalsetTimeoutsome(() => {})api.run(() => {})
现在即使你通过dependency change更新useEffect callback。假设你做了一些event listenersetInterval call。但是你有条件地这样做,如果已经运行了,就不要再设置它了。setInterval callbackevent listener callback不会访问最近的状态值。**为什么?**你已经猜到了。它们是created in the first runfirst 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 allThe 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 memoryreferred 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)。在这个意义上,唯一使我们有可能使用函数的是钩子。以及它们如何存储状态并将其带回来。

**一个很好的比喻是:**去一个办公室,做一些合同和交易。然后回来,进入同一个办公室,但等待不是真的同一个办公室,而是一个看起来一样的办公室。但一个新的办公室取代了它的位置(搬出去,搬进来,同样的活动)。你想知道为什么他们没有认出你。是的,有点偏离比喻。但仍然很好一点。

总的来说,我希望这给了你一个很好的感觉!

f0ofjuux

f0ofjuux4#

不应该将setInterval与钩子一起使用。看看React.js的维护者之一Dan Abramov在他的博客上对另一种方法的评论:https://overreacted.io/making-setinterval-declarative-with-react-hooks/

相关问题