1 首先老生常谈的闭包
function createIncrement(val) {
let value = 0;
function increment() {
value += incBy;
console.log(value);
}
const message = `Current value is ${value}`;
function log() {
console.log(message);
}
return [increment, log];
}
const [increment, log] = createIncrement(1);
increment(); // logs 1
increment(); // logs 2
increment(); // logs 3
// Does not work!
log(); // logs "Current value is 0"
[increment, log] = createIncrement(1) 返回了两个函数,一个是对value的累加,另一个是打印当前的value值。
当increment调用三次后value的值为3。
最终log()打印的message是“current value is 0”,这是不符合预期的value为3。
log()就是一个闭包,这个闭包捕获message变量。
尽管多次调用increment增加vulue的值,message变量也没有保持更新。
2 修复闭包
修复log函数使其打印更新后的value,移动message= ...;到log函数内部
function createIncrement(incBy) {
let value = 0;
function increment() {
value += incBy;
console.log(value);
}
function log() {
const message = `Current value is ${value}`;
console.log(message);
}
return [increment, log];
}
const [increment, log] = createIncrement(1);
increment(); // logs 1
increment(); // logs 2
increment(); // logs 3
// Works!
log(); // logs "Current value is 3"
现在log函数的value就是3了。
\
3 hooks中的闭包
3.1 useEffect()
function WatchCount() {
const [count, setCount] = useState(0);
useEffect(function() {
setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
}, []);
return (
<div>
{count}
<button onClick={() => setCount(count + 1) }>
Increase
</button>
</div>
);
}
多次点击button后,控制台的打印Count is 0, 实际上count已经被加了多次。
为什么会这样呢?
在第一次渲染的时候count被初始化为0
组件被挂载后,useEffect调用setInterval(log, 2000)每隔2秒打印一次,闭包log捕获的count变量是0。
即使count被增加多次,log闭包函数仍旧使用count=0的初始化渲染的值
我们知道useEffect的闭包log依赖count变量
function WatchCount() {
const [count, setCount] = useState(0);
useEffect(function() {
const id = setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
return function() {
clearInterval(id);
}
}, [count]);
return (
<div>
{count}
<button onClick={() => setCount(count + 1) }>
Increase
</button>
</div>
);
}
正确的设置依赖,useEffect随着count变化更新闭包
3.2 useState
function DelayedCount() {
const [count, setCount] = useState(0);
function handleClickAsync() {
setTimeout(function delay() {
setCount(count + 1);
}, 1000);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
</div>
);
}
快速点击button两次,count的值仍旧是1,而不是2
每次点击后延迟1秒调用delay函数,delay函数捕获的count始终是0
两个delay函数都是闭包,更新相同的value:setCount(count + 1) = setCount(0 + 1) = setCount(1)
两次点击后的闭包delay捕获的仍旧是更新前的count:0
为了修复这个问题,我们用函数的方式setCount(count => count + 1)更新count
function DelayedCount() {
const [count, setCount] = useState(0);
function handleClickAsync() {
setTimeout(function delay() {
setCount(count => count + 1);
}, 1000);
}
function handleClickSync() {
setCount(count + 1);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
<button onClick={handleClickSync}>Increase sync</button>
</div>
);
}
回调函数返回一个新的state依据之前的state。
4 总结
闭包问题经常捕获一个为更新的变量,一个有效的解决闭包问题的方法是在React hooks里设置正确的依赖,或者用函数的方式更新state