用函数闭包的思想去解释 Hooks 的 Capture Value 特性
useEffect 完整指南 中谈到过:
Effect拿到的总是定义它的那次渲染中的props和state。这能够避免一些bugs,但在一些场景中又会有些讨人嫌。对于这些场景,你可以明确地使用可变的ref保存一些值。
我们先来看一个 Capture Value
的例子:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
console.log(`You clicked ${count} times`);
}, 3000);
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
如果我点击了很多次并且在effect里设置了延时,打印出来的结果会是什么呢?
结果并不会是我们想的和类组件一样输出多个最新的 count 值。而是顺序的打印输出 — 每一个都属于某次特定的渲染,因此有它该有的count值。
如果我们把 Hooks 函数组件当成普通的函数,从函数闭包的角度就能很好的理解这一特性了。
let _state = 0;
function useState(initialValue) {
_state = _state | initialValue;
function setState(newState) {
_state = newState;
// 会重新执行组件函数
// render();
}
return [_state, setState];
}
function Component() {
const [count, setCount] = useState(0);
const handleClick = () => {
setTimeout(() => {
console.log("You clicked on: " + count);
}, 3000);
};
// 暴露页面上可以执行的函数
return [handleClick, setCount];
}
// 初次 render
const [handleClick, setCount] = Component();
// 模拟点击按钮
handleClick();
setCount(1); // 此时会重新执行 Component(),并返回新的 handleClick
setCount(2);
先理解一下闭包的一些概念:
函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。
也就是说,闭包可以让你从内部函数访问外部函数作用域。
在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
每次 render ,函数组件重新执行,生成并返回了和当前词法环境绑定的函数,函数组件闭包词法环境中的 state,props 是不会改变的。
当 handleClick() 执行时,输出的必然是初次 render 是的 count ,即 0 。3 秒之内无论多少次 setCount(),改变 count 的值,也只是每次 setCount() 后重新执行组件函数的新的词法环境的 count 值被改变了,并返回了新的 handleClick,并不会影响到第一次 render 闭包词法环境中的 count 。
所以打印结果是 You clicked on: 0
从而也解释了为什么 每一次渲染都有它自己的 Props、State and Effects,每一个组件内的函数(包括事件处理函数,effects,定时器或者API调用等等)会捕获某次渲染中定义的props和state。
以上只是个人的理解,可能有不正确的地方,欢迎指正,谢谢~