使用React Hooks之前必须注意的过时闭包陷阱

目录
文章目录隐藏
  1. 1.过时的闭包
  2. 2.修复过时的闭包
  3. 3. Hooks 中的过时闭包
  4. 4.总结

使用 React Hooks 之前必须注意的过时闭包陷阱

Hooks 简化了 React 组件内部状态和副作用的管理。 此外,可以将重复的逻辑提取到自定义 Hooks 中,以在整个应用程序中重复使用。

Hooks 严重依赖于 JS 闭包。这就是为什么 Hooks 如此具有表现力和简单,但是闭包有时很棘手。

使用 Hooks 时可能遇到的一个问题就是过时的闭包,这可能很难解决。

让我们从过时的装饰开始。 然后,看看到过时的闭包如何影响 React Hooks,以及如何解决该问题。

1.过时的闭包

工厂函数 createIncrement(incBy) 返回一个incrementlog函数的元组。 调用时,increment()函数将内部value增加incBy,而log()仅打印一条消息,其中包含有关当前value的信息:

function createIncrement(incBy) {
  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(); //  1
increment(); //  2
increment(); //  3
// 不能正确工作!
log();       //  "Current value is 0"

[increment, log] = createIncrement(1)返回一个函数元组:一个函数增加内部值,另一个函数记录当前值。

然后,increment()的 3 次调用将 value递增到 3。

最后,log()调用打印消息是 Current value is 0,这有点出乎意料的,因为此时 value 为 3 了。

log()是一个过时的闭包。闭包 log()捕获了值为 "Current value is 0"的 message 变量。

即使 value 变量在调用increment()时被增加多次,message变量也不会更新,并且总是保持一个过时的值 "Current value is 0"

过时的闭包捕获具有过时值的变量。

2.修复过时的闭包

修复过时的log()问题需要关闭实际更改的变量:value的闭包。

我们将语句 const 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(); //  1
increment(); //  2
increment(); //  3
// Works!
log();       // "Current value is 3"

现在,在调用了 3 次 increment() 函数之后,调用 log() 记录了实际value"Current value is 3"

3. Hooks 中的过时闭包

3.1 useEffect()

我们来看一下使用useEffect() 过时闭包的常见情况。

在组件<WatchCount>中,useEffect() 中每 2 秒记录一次count的值。

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>
  );
}

打开事例(codesandbox

并点击几次增加按钮。然后看看控制台,每 2 秒出现一次Count is: 0,尽管count状态变量实际上已经增加了几次。

使用 React Hooks 之前必须注意的过时闭包陷阱

为什么会这样?

第一次渲染时,状态变量count初始化为0

组件安装后,useEffect()调用 setInterval(log, 2000)计时器函数,该计时器函数计划每 2 秒调用一次log()函数。 在这里,闭包log()捕获到count变量为0

之后,即使在单击Increase按钮时count增加,计时器函数每 2 秒调用一次的log(),使用count的值仍然是0log()成为一个过时的闭包。

解决方案是让useEffect()知道闭包log()依赖于count,并在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>
  );
}

正确设置依赖项后,一旦count发生变化,useEffect()就会更新闭包。

3.2 useState()

<DelayedCount>组件有 1 个button ,以 1 秒延迟异步增加计数器。

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>
  );
}

现在打开演示(codesandbox。 快速单击 2 次按钮。 计数器仅更新为1,而不是预期的2

每次单击setTimeout(delay, 1000)将在 1 秒后执行delay()delay()此时捕获到的 count 为 0

两个delay()都将状态更新为相同的值: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>
  );
}

打开演示(codesandbox。 再次快速单击按钮2次。 计数器显示正确的值2

当一个返回基于前一个状态的新状态的回调函数被提供给状态更新函数时,React 确保将最新的状态值作为该回调函数的参数提供。

setCount(alwaysActualStateValue => newStateValue);

这就是为什么在状态更新过程中出现的过时装饰问题可以通过函数这种方式来解决。

4.总结

当闭包捕获过时的变量时,就会发生过时的闭包问题。

解决过时闭包的有效方法是正确设置 React 钩子的依赖项。或者,在失效状态的情况下,使用函数方式更新状态。

好了,以上就是这一次的分享,希望大家能收获一定的经验,避免以后在 Hooks 的使用中出现上面提到的这些问题。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 使用React Hooks之前必须注意的过时闭包陷阱

发表回复