我试图用按钮开始自动拔卡,并在croupierCount
达到17时停止它,使用setInterval
。croupierCount
的值变化(<div>
显示这个计数)与下面的这个函数,但是当我试图达到这个值在函数内停止间隔,它的记录值是0。你能帮我解决这个吗?
const TheGame = () => {
const [croupierCount, setCroupierCount] = useState(0);
const [croupierHand, setCroupierHand] = useState([]);
const onStandHandler = () => { // triggered with button
const croupierInterval = async () => {
let card = await fetchCard(deck); // fetching new card with every iteration (works)
croupierHand.push(card); // pushes fetched card (works)
if (card[0].value === 'ACE') {
setCroupierCount((prevCount) => prevCount + 11);
}
else if (card[0].value === 'JACK' || card[0].value === 'QUEEN' || card[0].value === 'KING') {
setCroupierCount((prevCount) => prevCount + 10);
}
else {
setCroupierCount((prevCount) => prevCount + Number(card[0].value));
};
// croupierCount is changing (I'm displaying it in div)
console.log(croupierCount); // croupierCount = 0, I don't know why.
if(croupierCount > 17) {
clearInterval(startInterval);
};
}
const startInterval = setInterval(croupierInterval, 1000);
};
};
你似乎错过了一个重要的点:在这里使用const
…
const [croupierCount, setCroupierCount] = useState(0);
…使croupierCount
为常数值,不管调用setCroupierCount
多少次。这个变量不能直接更新:你看到的更新实际上是React内部状态的变化,当组件被重新渲染时,用相同的名称表示——并且渲染函数被再次调用。
对于基于钩子的功能组件来说,这种不变性既是一种祝福,也是一种诅咒。
结果如下:
- 组件渲染时,第一次调用
TheGame
函数。它的useState
调用初始化value和相应的setter,作为与该组件实例绑定的内部React状态的一部分。
这些值是从useState
函数返回的,并且存储在TheGame
函数、croupierCount
和setCroupierCount
的局部变量(常量!)中。重要的是,这些特定的变量在每次调用TheGame
函数时都会重新创建,这一点经常被忽略。
则创建
onStandHandler
函数,将上述两个局部变量作为其作用域的一部分。在某个时刻,
onStandHandler
功能被触发(当用户按下按钮时)。它还创建了另一个函数croupierInterval
,它应该首先获取数据,然后通过调用setCroupierCount
来更新状态,并获取结果。
这个函数有两个问题。
首先,所有croupierInterval
看到的是当前croupierCount
和setCroupierCount
变量的值。它不能神奇地"窥视"到这些变量将携带的值,当渲染被触发和TheGame
函数执行下次-因为这些将是新的变量实际上!但是你似乎忽略了一个更大的问题:setInterval
不能很好地与fetch
(或任何异步动作)配合。您只需让JS周期性地触发该函数,而不是等待该操作的处理。
这不仅扰乱了预期的延迟(减慢fetch
,这样需要10秒,然后看看会发生什么),而且这里有一个实际的错误:由于clearInterval(startInterval)
不会停止处理await fetchCard(deck)
之后的函数的所有部分,在最坏的情况下,您的Croupier可能会超过17。
这是"为什么"的部分,但什么是"如何解决"?这里有几件事值得一试:
避免在功能组件中像瘟疫一样使用setInterval:通常有更好的替代品。特别是在本例中,您至少应该将设置调用
croupierInterval
绑定到前一个调用的完成useEffect
当你想要一些东西间接地改变你的状态作为某种副作用时。这不仅使您的代码更易于阅读和理解,而且还允许您清除副作用的副作用(如超时/间隔设置)也不要忘记处理人为错误:如果用户错误地双击了这个按钮会发生什么?
我张贴我的解决方案,如果有人遇到类似的问题。
const isMount = useRef(false);
...
useEffect(() => {
if (isMount.current && croupierCount !== 0) {
if (croupierCount < 17) {
setTimeout(() => {
onStandHandler();
}, 1000);
}
}
else isMount.current = true;
}, [croupierCount]);
const onStandHandler = async () => {
let card = await fetchCard(deck, 1);
croupierHand.push(card);
if (card[0].value === 'ACE') {
setCroupierCount(prevCount => prevCount + 11);
}
else if (card[0].value === 'JACK' || card[0].value === 'QUEEN' || card[0].value === 'KING') {
setCroupierCount(prevCount => prevCount + 10);
}
else {
setCroupierCount(prevCount => prevCount + Number(card[0].value));
};
};