今天在工作中遇到一个react hooks问题,虽然解决了但是对于其中的原因不太理解,这里发出来和大家一起讨论一下。
具体代码如下,场景步骤如下:
- 点击一次,执行running后,isPolling设为true,并触发polling的useEffect
- 快速再点击一次,执行running,因为running前执行了cancle,所以这时候running内的isPolling应该是false,但实际还是true
看了一些批量更新的知识,好像解释不了这种现象。大家有什么看法吗?
import { useCallback, useEffect, useState } from "react";
export default function MyApp() {
const [isPolling, setIsPolling] = useState(false);
const [pollingCount, setPollingCount] = useState(0);
const [timer, setTimer] = useState();
const handleClick = () => {
cancle();
running();
};
useEffect(() => {
if (isPolling && pollingCount <= 3) {
console.log("33");
clearTimeout(timer);
setTimer(
setTimeout(() => {
console.log("44");
running();
}, 1000)
);
}
}, [isPolling, pollingCount]);
const running = useCallback(() => {
console.log("running11:", isPolling, pollingCount);
if (!isPolling) {
console.log("11");
setIsPolling(true);
} else if (isPolling) {
console.log("22");
setPollingCount(pollingCount + 1);
}
}, [isPolling, pollingCount]);
const cancle = () => {
setIsPolling(false);
setPollingCount(0);
};
return (
<div>
<div onClick={handleClick}>点击</div>
</div>
);
}
执行结果如下
其实这个问题很基础,甚至还不涉及到任何框架,这里我用最简单的模型来简化一下你这个问题
isPolling
是一个常量,在同一个作用域内,无论使用什么方式,都不可能实现后续去改变它的值,这就回答了你的疑问,为什么调用setIsPolling(false)
后,isPolling
的值还是true
其实每次更新
useState
定义的状态,都会驱动函数组件
重新执行,在重新执行的作用域中,isPolling
的值才是更新后的值,那么在更新之前的上一次作用域中如何确保拿到更新后的值呢,React也提供了相应的方式,使用
setter
的回调React内部会在组件对应的
fiber
结构上维护一个更新队列,对于同一个状态连续的 setState 调用会形成一个循环链表,将上一次 setState 的结果传递给下一个 setState 的回调,因此通过回调的方式你能拿到上一次setIsPolling
的新值