乐趣区

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

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

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

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/qq44924588… 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

退出移动版