使用 React Hooks 時須要注意過期的閉包!

做者:Shadeed
譯者:前端小智
來源:dmitripavlutin
點贊再看,微信搜索 大遷世界,B站關注 前端小智這個沒有大廠背景,但有着一股向上積極心態人。本文 GitHub https://github.com/qq44924588... 上已經收錄,文章的已分類,也整理了不少個人文檔,和教程資料。

最近開源了一個 Vue 組件,還不夠完善,歡迎你們來一塊兒完善它,也但願你們能給個 star 支持一下,謝謝各位了。javascript

github 地址:https://github.com/qq44924588...前端

Hooks 簡化了 React 組件內部狀態和反作用的管理。 此外,能夠將重複的邏輯提取到自定義 Hooks 中,以在整個應用程序中重複使用。vue

Hooks 嚴重依賴於 JS 閉包。這就是爲何 Hooks 如此具備表現力和簡單,可是閉包有時很棘手。java

使用 Hooks 時可能遇到的一個問題就是過期的閉包,這可能很難解決。react

讓咱們從過期的裝飾開始。 而後,看看到過期的閉包如何影響 React Hooks,以及如何解決該問題。git

1.過期的閉包

工廠函數 createIncrement(incBy) 返回一個incrementlog函數的元組。 調用時,increment()函數將內部value增長incBy,而log()僅打印一條消息,其中包含有關當前value的信息:github

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狀態變量實際上已經增長了幾回。

image.png

爲何會這樣?

第一次渲染時,狀態變量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

爲了解決這個問題,咱們使用函數式方法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>
  );
}

打開演示(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和完善,你們面試能夠參照考點複習,另外關注公衆號,後臺回覆福利,便可看到福利,你懂的。

相關文章
相關標籤/搜索