我想在setinterval()中每秒更新一次状态,但是它不起作用。 我是刚接触钩子的新手,所以无法理解为什么会这样。 请看下面的代码片段,并给我建议。
// 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);
};
答案 0 :(得分:3)
之所以没有获得更新状态,是因为您在内部调用了它 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);
};
...
答案 1 :(得分:2)
您正在创建closure,因为gameStart()
会在useEffect挂钩运行时“捕获” gamePlayTime
的值一次,此后再也不会更新。
要解决此问题,必须使用React钩子状态更新的functional update pattern。与其直接向setGamePlayTime()
传递新值,不如将其传递给 function ,该函数在执行时会接收旧状态值并返回新值以进行更新。例如:
setGamePlayTime((oldValue) => {
const someNewValue = oldValue + 1;
return someNewValue;
});
尝试一下(基本上只是将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);
};
答案 2 :(得分:1)
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
Dan Abramov 文章很好地解释了如何使用钩子、状态和 setInterval() 类型的 api!
丹·阿布拉莫夫! React 维护团队的一员!如此出名,我个人爱他!
问题在于如何使用仅执行一次(第一次渲染)的 useEffect() 访问状态!
简短的回答是:通过使用引用(useRef)!另一个 useEffect() 在需要更新时再次运行!或者在每次渲染时!
让我解释一下!并检查 Dan Abramov 解决方案!最后你会得到更好的上述陈述!第二个例子与 setInterval() 无关!
=>
useEffect() 要么只运行一次,要么在每次渲染中运行!或者当依赖项更新时(当提供时)!
只有通过在每个相关时间运行和渲染的 useEffect() 才能访问状态!
或通过setState((state/*here the state*/) => <newStateExpression>)
但是如果你想访问 useEffect() 里面的状态 => 重新运行是必要的!意味着传递和执行新的回调!
这不适用于 setInterval!如果你每次都设置它!计数器重置!如果组件快速重新渲染,将导致无法执行!
如果你只渲染一次!状态未更新!作为第一次运行,运行一个回调!并关闭!状态是固定的! useEffect(() => { <run once, state will stay the same> setInterval(() => { <state fixed> }) }, [])
。
对于所有这种情况!我们需要使用 useRef! (refs)!
保存一个保持状态的回调!来自每次重新渲染的 useEffect()!或者通过在 ref 中保存状态值本身!视用途而定!
这就是你要找的!
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>;
}
我们可以看到他如何在每次重新渲染时不断保存新的回调!包含新状态的回调!
使用!这是一个干净简单的钩子!太美了!
请务必阅读丹的文章!正如他解释和解决了很多事情一样!
Dan Abramov 在他的文章中提到了这一点!
如果我们需要设置状态!在一个 setInteral 内!可以简单地将 setState() 与回调版本一起使用!
useState(() => {
setInterval(() => {
setState((state/*we have the latest state*/) => {
// read and use state
return <newStateExpression>;
})
}, 1000);
}, []) // run only once
我们甚至可以使用它!即使我们没有设置状态!可能的!虽然不好!我们只是返回相同的状态值!
setState((state) => {
// Run right away!
// access latest state
return state; // same value (state didn't change)
});
然而这会使不同的反应内部代码部分运行(1,2,3),并检查!最终摆脱重新渲染!很高兴知道!
我们仅在更新状态时使用它!如果不 !那我们就需要使用refs了!
展示如何使用引用和状态访问!让我们再举一个例子!这是另一种模式!在回调中传递状态!
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 uptated by this call
React.useEffect(() => {
setInteval(() => {
const state = getState();
// do what you want with the state!
// it works because of the ref! Get State return a value to the same ref!
// which is already updated
}, 1000)
}, []); // running only once
对于 setInterval()!好的解决方案是 Dan Abramov 钩子!为一个东西制作一个强大的自定义钩子是一件很酷的事情!第二个例子更多地展示了 refs 的用法和重要性,在这种状态下访问需要或问题!
很简单!我们总是可以定制一个钩子!使用参考!并更新 ref 中的状态!或者一个保持新状态的回调!视使用情况而定!我们在渲染上设置 ref(直接在自定义钩子中[在 render() 中执行的块])!或者在 useEffect() 中!在每次渲染时重新运行或根据依赖项重新运行!
注意useEffect()
useEffect => useEffect 异步运行,并在渲染绘制到屏幕后运行。
非常重要的一件事! useEffect() 在 render() 完成后运行,屏幕在视觉上更新!最后运行!你应该知道!
不过一般! Effects 应该在 useEffect() 上运行!所以任何自定义钩子都可以!因为它的 useEffect() 将在绘制之后和渲染 useEffect() 中的任何其他之前运行!如果不!就像需要直接在渲染中运行某些东西一样!那么你应该直接通过状态!有些人可能会通过回调!想象一些逻辑组件!并传递给它一个 getState 回调!不是一个好习惯!
如果你在某个地方做了一些这样有意义的事情!并谈论参考!确保 refs 更新正确!之前!
但通常您永远不会遇到问题!如果你这样做,那就是一种气味!您尝试采用的方式很高,可能不是正确的好方法!
总的来说,我希望给你一个很好的感觉!
答案 3 :(得分:0)
您不应将setInterval与钩子一起使用。看看React.js的维护者之一丹·阿布拉莫夫(Dan Abramov)关于他博客中的另一种说法:https://overreacted.io/making-setinterval-declarative-with-react-hooks/