说明
一提到 React hooks 的“闭包陷阱”大家可能想到的是 useEffect 中执行 setInterval 的例子:
function App() {
const [count, setCount] = useState(1);
useEffect(() => {
setInterval(() => {
console.log(count);
}, 1000);
}, []);
}
不管组件的其他地方使用 setCount 设置其他值,打印结果都是 1,大部分文章都是从 React hooks 和 React fiber 原理上解释“闭包陷阱”产生的原因。
这个例子在平时开发中基本不会使用。以 React hooks 原理的角度去解释这个问题反而增加了理解的难度。
原理
react hooks 的“闭包陷阱”其实 JavaScript 本身就存在,造成的原因是:JavaScript 采用的是静态作用域 静态作用域(static scope)是指声明的作用域是根据程序正文在编译时就确定的,有时也称为词法作用域(lexical scope)。 也就是说 JavaScript 函数定义的位置就决定了函数的作用域,结合闭包就出现“闭包陷阱”问题。 来看看这个例子:
<button id="addGlobalCount">globalCount add</button>
<div id="globalCount">globalCount: 0</div>
<button id="getCount">getCount</button>
<div id="count">count: 0</div>
<script>
let globalCount = 0;
const callback = (function () {
let cache;
return function (fn) {
if (!cache) {
cache = fn;
cache();
}
};
})();
function render() {
const count = globalCount;
document.getElementById("globalCount").innerHTML = "globalCount: " + count;
callback(function () {
document
.getElementById("getCount")
.addEventListener("click", function () {
document.getElementById("count").innerHTML = "count: " + count;
});
});
}
render();
document
.getElementById("addGlobalCount")
.addEventListener("click", function () {
globalCount++;
render();
});
</script>
点击 addGlobalCount 按钮后 globalCount 自增,并且执行 render 函数。render 函数中将全局 globalCount 变量赋值给 count 并打印在页面上,并且给 getCount 按钮绑定单击事件在页面输出 count 的值,这个函数只执行一次。
可以看到 count 的值一直是 0;addGlobalCount 每次点击执行 render 方法,但 callback 的参数函数只在第一次执行,后面并没有执行,getCount 按钮触发的单击函数始终是第一次绑定的函数,这个函数的作用域是它定义时的作用域也就是 count 为 0。
react hooks 的“闭包陷阱”也是这个原因。