这就不叫react hook闭包陷阱
使用react hook常发生的state拿不到最新值问题,本质是react函数组件带来的闭包导致的。
问题来源
话不多说,直接上代码描述被称为react hook闭包陷阱的问题:
const [a, setA] = React.useState(1);
console.log("a_comp", a);
React.useEffect(() => {
setA(2);
setTimeout(() => {
console.log("a", a);
}, 1000);
}, []);
代码输出为:
a_comp 1
a_comp 2
a 1
按直觉来讲,打印a的语句远晚于a的更新,a的值却不是最新的,这会导致一些意料之外的bug。
而所谓的直觉,便是来一下一下代码:
let a = 1;
setTimeout(() => {
console.log("a", a)
}, 100);
a = 2;
代码输出:
a 2
诚然 a = 1,这种赋值不是hook的赋值,setTimeout也没放在useEffect的回调里,是这两处差异导致最终打印的值不同吗?
网络上流行的解释是:
useEffect异步回调 or setTimeout的回调 带来的闭包将旧的a值保存到函数的作用域里,使得最终打印出来的是旧的值。
一般其他文章会分析hook的原理然后的出上述结论,但是只因为两个异步回调就导致输出值不同了吗?
我们把原生node代码也都放在异步回调里执行,代码如下:
let a = 1;
Promise.resolve().then(()=> {
console.log("beforeSetTimeout:", a)
setTimeout(() => {
console.log("a", a)
}, 100);
})
Promise.resolve().then(() => {
a = 2;
})
代码输出:
beforeSetTimeout: 1
a 2
还是无法复现react闭包陷阱。在setTimeout放入回调之前a确实是1,不过最终a还是取到了最新值。
这印证了一句话,闭包拿到的是值的引用,被引用的a变了,打印的a也变了,那为什么react hook中拿不到最新值呢?下一部分进入现场复原。
现场复原
直接上代码,本文不细究hook具体细节,只当他们是异步执行的函数,且初次执行和二次执行行为不同,下面实现了丐中丐版的useEffect和useState。
// 实现useEffect
let firstEffect = true;
const useEffect = (callback, deps) => {
if(deps.length === 0) {
if(firstEffect) {
setTimeout(() => {
callback();
}, 0)
firstEffect = false;
}
}
}
// 实现useState
let firstState = true;
let state;
const useState = (initValue) => {
if(firstState) {
state = initValue;
firstState= false;
}
const dispatch = (v) => {
state = v;
setTimeout(() => {
Component()
}, 50);;
}
return [state, dispatch];
}
// 实现函数组件
function Component() {
const [a, setA] = useState(1)
console.log("a_comp", a);
useEffect(() => {
setA(2);
setTimeout(() => {
console.log("a>>>", a);
}, 1000)
}, [])
}
Component();
代码输出:
a_comp 1
a_comp 2
a>>> 1
复现了!!!那么关键在哪里呢,我们保留了一部分函数组件的能力,这才能让你看的出这是react(手动狗头)。这部分能力就是函数中state更新时函数组件会重新执行,而我们发现,所谓保留的旧的a值,其实是函数组件重新执行带了的作用域隔绝效应,这时setTimeout中的回调只能拿旧的a值。
不信请看调试记录:
结论
-
所谓react hook闭包陷阱应该叫react函数组件闭包陷阱,因为这个闭包值是函数组件再次执行带来的。
-
看源码只是辅助,更重要的是实现细节。
转载自:https://juejin.cn/post/7206249233115332663