无法在反应钩子中更新 setInterval 内的状态

gol*_*nus 13 setinterval reactjs react-hooks

我想在 setinterval() 中每秒更新一次状态,但它不起作用。我是 React hook 的新手,所以无法理解为什么会发生这种情况。请查看以下代码片段并给我建议。

// 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);
  };

Run Code Online (Sandbox Code Playgroud)

Moh*_*lal 13

https://overreacted.io/making-setinterval-declarative-with-react-hooks/

\n

Dan Abramov 文章很好地解释了如何使用钩子、状态和 API 的 setInterval() 类型!

\n

丹·阿布拉莫夫!是React维护团队之一!如此出名,我个人很爱他!

\n

快速解释

\n

useEffect()问题是如何访问只执行一次(第一次渲染)的状态的问题!

\n
\n

注意:要深入解释为什么状态未在useEffect callback和其他inside useEffect callbacks. 检查关于闭包和反应重新渲染的最后一节......

\n
\n

简短的答案是:通过使用引用(useRef)!另一个 useEffect() 在需要更新时再次运行!或者在每次渲染时!

\n

让我解释!并检查丹·阿布拉莫夫的解决方案!最后你会更好地理解上面的陈述!第二个例子与 setInterval() 无关!

\n

=>

\n

useEffect()要么只运行一次,要么在每个渲染中运行!或者当依赖项更新时(如果提供)!

\n

只能通过每次相关时间运行和渲染的useEffect()来访问状态!

\n

或者通过setState((state/*here the state*/) => <newStateExpression>)

\n

但如果你想访问 useEffect() => 内部的状态,则需要重新运行!意味着传递并执行新的回调!

\n

这与 setInterval 配合不好!如果你每次都清除并重新设置!计数器重置!如果组件快速重新渲染,将导致不执行!而且毫无意义!

\n

如果只渲染一次!状态未更新!作为第一次运行,运行一个回调!并进行关闭!状态已定!useEffect(() => { <run once, state will stay the same> setInterval(() => { <state fixed as closure of that time> }) }, [])

\n

对于所有这样的情况!我们需要使用useRef!(参考文献)!

\n

将保存状态的回调保存到其中!来自useEffect()每次重新渲染的!或者通过将状态值本身保存在ref! 根据用途而定!

\n

Dan abramov 的 setInterval 解决方案(简单干净)

\n

这就是您正在寻找的!

\n

useInteval 钩子(丹·阿布拉莫夫)

\n
import React, { useState, useEffect, useRef } from \'react\';\n\nfunction useInterval(callback, delay) {\n  const savedCallback = useRef();\n\n  // Remember the latest callback.\n  useEffect(() => {\n    savedCallback.current = callback;\n  }, [callback]);\n\n  // Set up the interval.\n  useEffect(() => {\n    function tick() {\n      savedCallback.current();\n    }\n    if (delay !== null) {\n      let id = setInterval(tick, delay);\n      return () => clearInterval(id);\n    }\n  }, [delay]);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

用法

\n
import React, { useState, useEffect, useRef } from \'react\';\n\nfunction Counter() {\n  let [count, setCount] = useState(0);\n\n  useInterval(() => {\n    // Your custom logic here\n    setCount(count + 1);\n  }, 1000);\n\n  return <h1>{count}</h1>;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们可以看到他如何在每次重新渲染时保存新的回调!包含新状态的回调!

\n

要使用!这是一个干净简单的钩子!这就是美啊!

\n

请务必阅读丹的文章!他解释并解决了很多事情!

\n

设置状态()

\n

丹·阿布拉莫夫在他的文章中提到了这一点!

\n

如果我们需要设置状态!在一组内部!我们可以简单地使用 setState() 和回调版本!

\n
useState(() => {\n   setInterval(() => {\n      setState((state/*we have the latest state*/) => {\n          // read and use state\n          return <newStateExpression>;\n      })\n\xc2\xa0  }, 1000);\n}, []) // run only once\n
Run Code Online (Sandbox Code Playgroud)\n

我们甚至可以使用它!即使我们没有设置状态!可能的!虽然不太好!我们只是返回相同的状态值!

\n
setState((state) => {\n   // Run right away!\n   // access latest state\n   return state; // same value (state didn\'t change)\n});\n
Run Code Online (Sandbox Code Playgroud)\n

然而,这将使不同的反应内部代码部分运行(1、2、3 )并检查最终从重新渲染中退出!很高兴知道!

\n

我们仅在更新状态时使用它!如果不!那么我们需要使用ref!

\n

另一个例子:使用 getter 版本的 useState()

\n

来展示how to work with refs and state access! 我们再举一个例子!这是另一种模式!在回调中传递状态!

\n
import React from \'react\';\n\nfunction useState(defaultVal) {\n    // getting the state\n    const [state, setState] = React.useState(defaultValue);\n    // state holding ref\n    const stateRef = React.useRef();\n    stateRef.current = state; // setting directly here!\n    // Because we need to return things at the end of the hook execution\n    // not an effect\n\n    // getter\n    function getState() {\n       // returning the ref (not the state directly) \n       // So getter can be used any where!\n       return stateRef.current;\n    } \n    \n    return [state, setState, getState];\n}\n
Run Code Online (Sandbox Code Playgroud)\n

该示例属于同一类别!但这里没有效果!

\n

但是,我们可以使用上面的钩子来访问钩子中的状态,如下所示!

\n
const [state, useState, getState] = useState(); // Our version! not react\n// ref is already updated by this call\n\nReact.useEffect(() => {\n  setInteval(() => {\n      const state = getState();\n      // do what you want with the state!\n      // it works because of the ref! Get State to return a value to the same ref!\n     // which is already updated \n  }, 1000)\n}, []); // running only once\n\n
Run Code Online (Sandbox Code Playgroud)\n

为了setInterval()!好的解决方案是丹·阿布拉莫夫钩子!为某样东西制作一个强大的定制挂钩是一件很酷的事情!第二个例子更多地展示了 refs 的用法和重要性,在这种状态下访问需要或有问题!

\n

这很简单!我们总是可以定制一个钩子!使用参考!并更新状态ref!或者保存新状态的回调!根据用途而定!我们在渲染器上设置引用(直接在自定义钩子中[块在 render() 中执行])!或者在一个useEffect()!在每次渲染时重新运行或根据依赖项重新运行!

\n

关于 useEffect() 和 refs 设置的注意事项

\n

关于useEffect()的注意事项

\n

useEffect => useEffect 异步运行,并在屏幕上绘制渲染后运行。

\n
    \n
  • 您以某种方式导致渲染(更改状态或父级重新渲染)
  • \n
  • React 渲染你的组件(调用它)
  • \n
  • 屏幕视觉更新
  • \n
  • 然后useEffect运行
  • \n
\n

非常重要的一件事useEffect()render() 完成后运行并且屏幕在视觉上更新!它最后运行!你应该知道!

\n

不过一般来说!效果应该在 useEffect() 上运行!所以任何自定义钩子都可以!因为它useEffect()会在绘制之后、渲染 useEffect() 中的任何其他操作之前运行!如果不!就像需要直接在渲染中运行某些东西一样!那你应该直接通过state!有些人可能会通过回调!想象一些逻辑组件!并且 getState 回调被传递给它!这不是一个好的做法!
\n如果你在某个地方做了一些类似的事情!并谈论ref!确保refs更新正确!还有之前!

\n

但一般来说,你永远不会有问题!如果你这样做了那就是一种气味!您尝试采用的方式很高,可能不是正确的好方法!

\n

关闭等等。为什么状态变量没有最新值?

\n

为什么不能简单地直接访问状态值可以归结为closure notion. Render function at every re-render go totally with a new call。每个调用都有其关闭。在每次重新渲染时,useEffect callback都是一个新的匿名函数。具有新的价值范围。

\n

问题就在这里dependency array。到access the closure of this call。所以这个电话的最近状态。你必须useEffect利用new callback. 如果依赖关系发生变化,就会发生这种情况。如果没有那就不会了。

\n

如果你这样做,[]那么useEffect()只会运行第一次。第一次渲染后的每个新调用。总会useEffect得到一个new anonymous function。但它们都没有效果或被使用(运行)。

\n

同样的概念适用于useCallback和 许多other hooks。而这一切都是结果closures

\n

useEffect 回调中的回调

\n
\n

前任:event listnerssetIntervalsetTimeoutsome(() => {})api.run(() => {})

\n
\n

现在即使你更新了useEffect callbackthrough dependency change. 假设您做了一些event listenersetInterval call. 但你是有条件地这样做的,所以如果已经运行就不再设置它。或setInterval callback无法event 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 部分。他们让国家关闭是有时间价值的。And never get to see or know about any other re-render call**Unless somehow you. inject something within. That 仍然引用可以访问创建时的or最新值闭包(references or globals). And those values come from the hooks (useState) and their internal machinery. All it would have is the.**

\n

有趣的比喻

\n

大多数人都会陷入这个陷阱。查看代码并说嘿为什么当状态更新时它没有获得更新的值。答案是。状态值来自 useState,它不是全局变量。即使您正在查看相同的代码。最初的呼唤和后来的呼唤都是不同的空间(众生)。从这个意义上说,唯一使我们能够使用函数的就是钩子。以及他们如何存储状态并将其恢复。

\n

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

\n

总的来说,我希望这能给您带来良好的感觉!

\n


小智 7

您不应该将 setInterval 与 hooks 一起使用。看看 React.js 的维护者之一 Dan Abramov 在他的博客上关于替代方案的言论:https://overreacted.io/making-setinterval-declarative-with-react-hooks/


jer*_*red 6

您正在创建一个闭包,因为在 useEffect 挂钩运行时gameStart()“捕获”了gamePlayTime一次的值,并且此后不再更新。

为了解决这个问题,你必须使用React 钩子状态更新的功能更新模式。不是直接将新值传递给setGamePlayTime(),而是传递给它一个函数,该函数在执行时接收旧状态值并返回一个新值以进行更新。例如:

setGamePlayTime((oldValue) => {
  const someNewValue = oldValue + 1;
  return someNewValue;
});
Run Code Online (Sandbox Code Playgroud)

试试这个(基本上只是用功能状态更新包装你的 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);
  };
Run Code Online (Sandbox Code Playgroud)


Coo*_*Dev 6

你没有得到更新状态的原因是因为你在 useEffect(() => {}, []) 中调用了它,它只被调用了一次。

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);
  };
...
Run Code Online (Sandbox Code Playgroud)