关于javascript:使用-React-Hooks-时需要注意过时的闭包

40次阅读

共计 4088 个字符,预计需要花费 11 分钟才能阅读完成。

作者:Shadeed
译者:前端小智
起源:dmitripavlutin

点赞再看 ,微信搜寻【大迁世界】,B 站关注【前端小智】 这个没有大厂背景,但有着一股向上踊跃心态人。本文 GitHub https://github.com/qq44924588… 上曾经收录,文章的已分类,也整顿了很多我的文档,和教程材料。

最近开源了一个 Vue 组件,还不够欠缺,欢送大家来一起欠缺它,也心愿大家能给个 star 反对一下,谢谢各位了。

github 地址:https://github.com/qq44924588…

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,这有点出其不意的,因为此时 value3 了。

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

关上事例(https://codesandbox.io/s/stale-closure-use-effect-broken-2-gyhzk)

并点击几次减少按钮。而后看看控制台,每 2 秒呈现一次 Count is: 0,只管count 状态变量实际上曾经减少了几次。

为什么会这样?

第一次渲染时,状态变量 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>
  );
}

当初关上演示(https://codesandbox.io/s/use-…。疾速单击 2 次按钮。计数器仅更新为1,而不是预期的2

每次单击 setTimeout(delay, 1000) 将在 1 秒后执行 delay()delay() 此时捕捉到的 count0

两个 delay() 都将状态更新为雷同的值:setCount(count + 1) = setCount(0 + 1) = setCount(1)

这是因为第二次单击的 delay() 闭包中已捕捉了过期的 count 变量为0

为了解决这个问题,咱们应用函数式办法 s etCount(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>
  );
}

关上演示 (https://codesandbox.io/s/use-…。再次疾速单击按钮2 次。计数器显示正确的值2

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

setCount(alwaysActualStateValue => newStateValue);

这就是为什么在状态更新过程中呈现的过期装璜问题能够通过函数这种形式来解决。

4. 总结

当闭包捕捉过期的变量时,就会产生过期的闭包问题。

解决过期闭包的无效办法是正确设置 React 钩子的依赖项。或者,在生效状态的状况下,应用函数形式更新状态。

~ 完,我是小智,我要去刷碗了。


代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。

原文:https://dmitripavlutin.com/re…

交换

文章每周继续更新,能够微信搜寻「大迁世界」第一工夫浏览和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 曾经收录,整顿了很多我的文档,欢送 Star 和欠缺,大家面试能够参照考点温习,另外关注公众号,后盾回复 福利,即可看到福利,你懂的。

正文完
 0