閉包概念是掌握React.JS的關鍵 - Nitsan

banq發表於2021-07-24

在本文中,展示理解閉包如何提高您的 React 技能。
閉包意味著,當 JavaScript 執行您的程式碼時,它會查詢您函式中的所有變數,如果它在函式內部沒有宣告, 但在外部作用域(函式巢狀的地方)它“鎖定”指定函式內該變數的值。

const myOuterFunction = () => { 
  let variableInOuterFunction = 'Hello World';
  const myNestedFunction = () => { 
    console.log(variableInOuterFunction); 
  };
  myNestedFunction(); 
};
myOuterFunction();


請注意我們如何在“myNestedFunction”中使用“variableInOuterFunction”的(在 myOuterFunction 中宣告)。那是一個閉包。值“Hello World”現在在巢狀函式中“閉包”(鎖定)。
聽起來很簡單,對吧?讓我們看看理解它如何幫助我們在 React 中。
 

React 中的閉包
所以,讓我們看看閉包在 React 中是如何工作的。

import React, { useEffect, useState } from 'react';
const ClosuresInReact = () => {
  const [count, setCount] = useState('1');
  const incrementCount = () => {
    setCount(prevCount => +prevCount + 1);
  };
  useEffect(() => {
    const timer = setTimeout(() => {
      incrementCount();
    }, 1000);
    return () => {
      clearTimeout(timer);
    };
  }, [incrementCount]);
  return <div>{`Timer started: ${count}`}</div>;
};
export default ClosuresInReact;


這是一個簡單的 React 元件,它在螢幕上列印一個計時器,該計時器每秒遞增。
我們使用 useState 鉤子來跟蹤 count 變數的變化,然後我們有 incrementCount 函式,它使用 setCount 函式增加 count 變數,最後,我們有 useEffect 鉤子,負責每秒觸發 incrementCount 函式,使用 JavaScript 內建的函式 setTimeout。
有經驗的 React 開發人員可能已經注意到,我們在 useEffect 的依賴陣列中掛鉤了函式 incrementCount。實際上,可能沒有必要在此處包含它,但始終使用 useEffect 鉤子中使用的所有變數(和函式)填充依賴項陣列被認為是一種很好的做法。
現在我們有另一個問題。使用 incrementCount 函式作為 useEffect 鉤子中的依賴項,我的 IDE 向對我大喊:Add alt text
我們現在不會深入探討為什麼會發生這種情況,我只想說這與函式是引用型別的事實有關,如果我們不將它移動到 useEffect 鉤子內或用 useCallback 鉤子包裝它,我們可以導致無限迴圈。無論如何,對我們來說,現在重要的是來自 IDE 的建議——要麼將 incrementCount 移動到 useEffect 中,要麼用 useCallback 鉤子包裝它。讓我們選擇第二個選項。

import React, { useCallback, useEffect, useState } from 'react';
const ClosuresInReact = () => {
  const [count, setCount] = useState('1');
  const incrementCount = useCallback(() => {
    setCount(prevCount => +prevCount + 1);
  }, []);
  useEffect(() => {
    const timer = setTimeout(() => {
      incrementCount();
    }, 1000);
    return () => {
      clearTimeout(timer);
    };
  }, [incrementCount]);
  return <div>{`Timer started: ${count}`}</div>;
};
export default ClosuresInReact;


現在一切都應該正常了,對吧?我的意思是即使我的 IDE 現在也平靜了,讓我們執行程式碼吧!
我們看到 Timer started:1, 2 和……發生了什麼?計時器剛停在 2 點?現在,為什麼會這樣?
原因——你猜對了——閉包!如果我們仔細觀察,我們會發現在 incrementCount 函式中我們實際上有一個閉包。
在外部函式 ClosuresInReact 中宣告的變數 count 的值現在被“鎖定”在函式 incrementCount 中。更準確地說,我們在 setCount 函式中所做的增量被鎖定在 incrementCount 函式內,這就是我們在螢幕上看到 1、2 和...停止的原因。
 

如何修復 React 中的閉包問題
現在我有一個 useEffect 鉤子,它有 incrementCount 作為依賴項。這意味著我應該只在 incrementCount 改變時重新渲染。我用 useCallback 鉤子包裹了 incrementCount。這意味著我應該記住它的第一個閉包(當它第一次執行時),即使發生重新渲染,我也不應該使用重新整理後的環境(計數現在為 2)執行該函式,而是使用我捕獲的舊的第一個閉包(當計數為 1 時)。
useEffect 告訴我 incrementCount 沒有改變,我不應該觸發新的渲染。
解決方法很簡單。我們需要將 count 變數作為依賴項傳遞給 useCallback 鉤子。透過這個,我們告訴 React,即使我們想要保留 incrementCount 函式的第一個閉包,React 也應該使用 count 變數的最新值來跟蹤和更新閉包。透過這樣做,我們還能讓 useEffect 鉤子在螢幕上呼叫重新渲染,因為 incrementCount 每秒都在變化。

import React, { useCallback, useEffect, useState } from 'react';
const ClosuresInReact = () => {
  const [count, setCount] = useState('1');
  const incrementCount = useCallback(() => {
    setCount(prevCount => +prevCount + 1);
  }, [count]);
  useEffect(() => {
    console.log('useEffect');
    const timer = setTimeout(() => {
      incrementCount();
    }, 1000);
    return () => {
      clearTimeout(timer);
    };
  }, [incrementCount]);
  return <div>{`Timer started: ${count}`}</div>;
};
export default ClosuresInReact;

現在一切都按預期工作。有趣的是,我的 IDE 現在仍然對我大喊大叫:Add alt text
它告訴我要從依賴陣列中刪除計數count,這當然是一個錯誤,它的發生是因為我們實際上並沒有在 setCount 函式中使用 count 變數,但這只是再次向我們展示了理解閉包對於成為一名優秀的 React 開發人員的重要性,因為沒有它我們就無法解決這個問題。



 

相關文章