无法使用 setInterval 在异步函数中达到值 - JavaScript



我试图用按钮开始自动拔卡,并在croupierCount达到17时停止它,使用setIntervalcroupierCount的值变化(<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函数、croupierCountsetCroupierCount的局部变量(常量!)中。重要的是,这些特定的变量在每次调用TheGame函数时都会重新创建,这一点经常被忽略。

!
  • 则创建onStandHandler函数,将上述两个局部变量作为其作用域的一部分。

  • 在某个时刻,onStandHandler功能被触发(当用户按下按钮时)。它还创建了另一个函数croupierInterval,它应该首先获取数据,然后通过调用setCroupierCount来更新状态,并获取结果。

这个函数有两个问题。

首先,所有croupierInterval看到的是当前croupierCountsetCroupierCount变量的值。它不能神奇地"窥视"到这些变量将携带的值,当渲染被触发和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));
};    
};

最新更新