最通俗易懂的解释 react hooks 闭包陷阱

831 阅读1分钟

说明

一提到 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 的值,这个函数只执行一次。

5.gif

可以看到 count 的值一直是 0;addGlobalCount 每次点击执行 render 方法,但 callback 的参数函数只在第一次执行,后面并没有执行,getCount 按钮触发的单击函数始终是第一次绑定的函数,这个函数的作用域是它定义时的作用域也就是 count 为 0。

react hooks 的“闭包陷阱”也是这个原因。