<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      理解 React 中的 useEffect、useMemo 與 useCallback

      useEffect

      先理解 useEffect 有助于學習 useMemo 和 useCallback。因為 useMemo 和 useCallback 的實現實際上都是基于 useEffect 的。

      useEffect 是 React 中的一個很重要的 Hook,用于執行副作用操作。什么是副作用?簡單來說,就是那些會改變函數外部變量或有外部可觀察影響的操作。useEffect 允許你在函數組件中執行副作用操作。它會在組件每次渲染后執行副作用函數。如果指定了 deps 數組,則只有當 deps 中的某個值變化時才會重新運行副作用函數。

      常見的副作用操作有:

      • 訂閱數據:訂閱某個數據源,當數據變化時更新組件 state。
      • 手動更改 DOM: 通過訪問 DOM 節點或使用第三方 DOM 庫來改變 DOM 結構。
      • 日志記錄:在控制臺打印日志信息。
      • 計時器:通過設置 Interval 或 Timeout 來執行定時操作。
      • 事件監聽:為 DOM 節點添加或移除事件監聽器。

      useEffect 的兩個參數

      useEffect 根據依賴項確定是否重新執行。它接收兩個參數:

      1. effect 函數:執行副作用操作的函數。
      2. deps 數組 (可選):effect 函數的依賴項數組。如果指定了 deps,那么只有當 deps 中的某個值發生變化時,effect 才會被重新執行。
      useEffect (() => {
        // 副作用操作
      }, [deps])  // 如果指定了 deps, 則只有 deps 的值變化時才重新執行
      

      舉個例子:

      useEffect (() => {
        document.title = `你點擊了 ${count} 次` 
      }, [count])  // 僅當 count 變化時重新設置 document.title
      

      這里的 effect 函數是設置 document.title,deps 是 [count]。這意味著只有當 count 值變化時,effect 才會重新執行,否則它會被跳過。

      如果你傳入一個空數組 [] 作為第二個參數,那么 effect 將只在第一次渲染時執行一次:

      useEffect (() => {
        document.title = `Hello!`
      }, [])
      

      這里的 effect 函數僅在組件初始渲染時執行一次,因為 [] 意味著該 effect 沒有任何依賴項。

      如果你不指定第二個參數,那么 effect 將在每次渲染后執行:

      useEffect (() => {
        document.title = `你點擊了 ${count} 次`
      })
      

      這里的 effect 在每次渲染后都會執行,因為我們沒有指定 deps。

      總結一下,useEffect 的兩個參數:

      • effect 函數:執行副作用操作的函數。
      • deps 數組 (可選):effect 函數的依賴項數組。
        • 如果指定了 deps,那么只有 deps 中的值變化時,effect 才會重新執行。
        • 如果傳入 [] 作為 deps,則 effect 只會在第一次渲染時執行。
        • 如果不指定 deps,則 effect 將在每次渲染后執行。

      理解 effect 函數和 deps 數組是使用 useEffect 的關鍵。effect 負責執行具體的副作用操作,而 deps 控制 effect 的執行時機。

      會執行兩次的 useEffect

      在開發環境下,useEffect 第二個參數設置為空數組時,組件渲染時會執行兩次。這是因為 React 在開發模式下會執行額外的檢查,以檢測 Trumpkin 警告并給出更好的錯誤信息。當你指定 [] 作為 deps 時,這意味著 effect 沒有任何依賴項,所以它應該只在組件掛載時執行一次。但是,在第一次渲染時,React 無法確定 deps 是否會在將來的渲染中發生變化。所以它會在初始渲染時執行一次 effect,然后在 “調用階段” 再執行一次,以確保如果 deps 發生變化,effect 也會再次執行。如果在 “調用階段” 重新渲染時 deps 仍然為 [],那么 React 會更新內部狀態,記錄該 effect 確實沒有依賴項,并在將來的渲染中跳過 “調用階段” 重新執行的步驟。

      這就是在開發環境下 effect 會執行兩次的原因。這種行為只在開發模式下發生,在生產模式下 effect 只會執行一次。目的是為了提高開發體驗,給出更清晰的錯誤提示。如果 effect 的 deps 發生變化但沒有再次執行,React 可以明確地給出警告。而在生產模式下,這樣的檢查是不必要的,所以 effect 只會執行一次以減少性能開銷。

      總結一下:當你在開發環境下使用 useEffect 并指定 [] 作為依賴項時,effect 函數會在初始渲染時執行兩次。這是因為 React 會在 “調用階段” 再次執行 effect,以檢查依賴項是否發生變化,給出更清晰的警告信息。如果 deps 仍然為 [],那么 React 會更新狀態并在將來跳過 “調用階段” 的重新執行。這種行為只在開發模式下發生,生產模式下 effect 只會執行一次。

      什么是 Trumpkin 警告?

      Trumpkin 警告是 useEffect Hook 的一種錯誤警告。它會在開發環境下出現,用來表示 effect 函數中使用的某個狀態或 props 在依賴項 deps 中遺漏。

      比如:

      function Counter () {
        const [count, setCount] = useState (0);
        
        useEffect (() => {
          document.title = `You clicked ${count} times`;
        });  // 沒有指定 deps
      }
      

      這里,effect 函數使用了 count state,但我們沒有將它添加到 deps 中。所以 React 會在開發環境下給出 Trumpkin 警告: React Hook useEffect has a missing dependency: 'count'. Either include it or remove the dependency array.

      這是為了提示我們 count 狀態發生變化時,effect 函數并不會重新執行,這很可能是個 bug。要修復這個警告,我們有兩種選擇:

      1. 添加 count 到 deps:
      useEffect (() => {
        document.title = `You clicked ${count} times`;
      }, [count]); 
      
      1. 如果 effect 不依賴任何值,傳入空數組 []:
      useEffect (() => {
        document.title = `You clicked ${count} times`;  
      }, []);
      

      為什么說此時可能是個 bug?當你不指定 useEffect 的第二個參數 (deps) 時,effect 回調函數會在每次渲染后執行。但是,這并不意味著 effect 中使用的所有狀態和 props 都會在 effect 重新執行時更新。 effect 執行時所使用的變量會被創建出一個閉包,它會捕獲 effect 創建時那一刻變量的快照。所以,如果 effect 使用了某個狀態,但沒將其添加到依賴項 deps 中,當那個狀態更新時,effect 中仍然會使用舊的值。 這很可能導致 bug。

      function Counter () {
        const [count, setCount] = useState (0);
        
        useEffect (() => {
          document.title = `You clicked ${count} times`;  // 使用了 count 但沒有指定為依賴
        }); 
      }
      

      這里,effect 中使用了 count 狀態,但是我們沒有將它添加到 deps 中。在第一次渲染時,count 為 0,所以 document.title 會被設置為 "You clicked 0 times"。如果我們隨后將 count 更新為 1, 你可能會期望 document.title 也變為 "You clicked 1 times"。但是,當 effect 被執行時,它會捕獲 count 的 “舊值” 0。所以 document.title 實際上仍然會是 "You clicked 0 times"。 count 的更新并沒有觸發 effect 的重新執行。

      這就是 Trumpkin 警告出現的原因,React 會檢測到 effect 中使用了某個狀態,但沒有在依賴項 deps 中指定它,這很有可能導致 bug。 所以 Trumpkin 警告的目的是在開發環境下檢測這樣的錯誤,并給出清晰的提示以修復它們。

      了解 React 中的 “調用階段”

      React 在初次渲染后會再次執行 useEffect Hook 的調用,以校驗是否有依賴項被遺漏從而產生 Trumpkin 警告。在上例中,React 在第一次渲染時會執行一次 effect,然后在 “調用階段” 再次執行 effect。這時,它會檢測到 count 狀態被使用但未在 deps 中指定,所以會產生 Trumpkin 警告。如果 deps 指定為 [],在 “調用階段” 的重新執行中它會檢測到 deps 沒有變化,所以會更新內部狀態并在將來的渲染中跳過這個額外步驟(調用階段)。

      useEffect 的實現

      effect 函數會創建一個閉包,捕獲函數內部使用的所有狀態和 props 的值。這是因為 Javascript 中的函數會隱式創建閉包。當 effect 第一次執行時,它會讀取函數內使用的所有狀態和 props,并將其值保存到閉包中。

      舉個例子:

      function Counter () {
        const [count, setCount] = useState (0);
        
        useEffect (() => {
          const foo = count;  // 讀取 count 并存入閉包
          document.title = `You clicked ${foo} times`;  
        });
      }
      

      這里,foo 變量是定義在 effect 函數內部的。當 effect 第一次執行時,它會讀取 count 的當前值 0,并將其保存到 foo 中。foo 變量及其所捕獲的 0 值都被保存在 effect 的閉包中。即使后續我們將 count 更新為 1,當 effect 重新執行時,它仍然會在閉包中找到 foo 變量,其值為 0。所以 document.title 不會更新。除非我們指定 [count] 作為 effect 的依賴項:

      useEffect (() => {
        const foo = count;
        document.title = `You clicked ${foo} times`;
      }, [count]);
      

      現在,每當 count 更新時,effect 會重新執行。它會再次讀取 count 的最新值,并將其保存到閉包的 foo 中:

      • 第一次執行:count 為 0,foo 被設置為 0
      • count 更新為 1:effect 重新執行,讀取 count 為 1,將其保存到 foo 中,覆蓋之前的值
      • 以此類推...

      要實現這個效果,有兩個關鍵點:

      1. Javascript 函數會隱式創建閉包,用來存儲函數內定義的變量和其值。
      2. effect 會在第一次執行時讀取所有使用的狀態和 props 的值,并將其保存到閉包中。除非 deps 發生變化,否則 effect 在重新執行時會使用閉包中的 “舊值”。

      這就是 effect 如何通過閉包捕獲變量值的實現機制。理解這一點,以及如何通過依賴項 deps 避免使用 “舊值” 導致的 bug,是使用 useEffect 的關鍵。

      在 useEffect 中指定了 deps 依賴項時,它會在 deps 中的任何值變化時重新運行 effect 函數。這時,它會重新讀取最新的值,而不是使用閉包中的 “舊值”。這是通過在 effect 函數內部重新聲明狀態和 props 的值來實現的。每當 effect 重新運行時,它會捕獲那一刻的最新值,然后替換閉包中的 “舊值”。

      舉個例子:

      function Counter () {
        const [count, setCount] = useState (0);
        
        useEffect (() => {
          const foo = count;  // 重新讀取 count 的最新值
          document.title = `You clicked ${foo} times`;  
        }, [count]);  // 指定 count 作為依賴項
      }
      

      在第一次執行時,foo 被設置為 count 的初始值 0。當我們更新 count 為 1 時,effect 會重新運行,因為我們指定了 [count] 作為依賴項。這時,effect 會再次讀取 count,現在其值為 1。它會將 1 賦值給 foo,覆蓋閉包中的 “舊值” 0。所以每當 effect 重新運行時,它都會重新讀取狀態和 props 的最新值,并更新閉包中的值。這確保了在 effect 函數中,我們總是使用的是最新的,而不是舊的閉包值。在 React 源碼中,這是通過在 effect 重新運行時調用 create 子函數來實現的:

      function useEffect (create, deps) {
        //...
        function recompute () {
          const newValue = create ();  // 重新運行子函數,讀取最新值
          storedValue.current = newValue;  // 更新閉包中的值
        }
        
        if (depsChanged) recompute ();   // 如果 deps 變化,重新計算
      }
      

      每當依賴項 deps 變化時,React 會調用 recompute 函數來重新運行 create 子函數。create 會讀取最新的狀態和 props 值,并將新值保存到存儲變量 storedValue 中,覆蓋之前的值。所以,通過在 effect 重新運行時重新讀取值并更新存儲變量,React 確保你總是在 effect 函數中使用最新的 props 和狀態,而不是閉包捕獲的 “舊值”。這就是 useEffect 在指定了 deps 依賴項時如何避免使用閉包中的 “舊值” 的實現機制。

      每當 deps 變化,它會重新運行 effect 并讀取最新的值,更新存儲在閉包中的值。當你不指定 useEffect 的依賴項 deps 時,effect 函數會在每次渲染后運行。這時,effect 在重新運行時會繼續使用閉包中的 “舊值”,而不是讀取最新的狀態和 props 值。這是因為沒有指定依賴關系,所以 React 認為 effect 不依賴于任何值的變化。在源碼中,這是通過不調用 recompute 函數來實現的。recompute 函數負責在依賴項變化時重新運行 effect 并更新閉包值。所以簡單來說,當你不指定 deps 時,effect 在重新運行時什么也不會做 —— 它會繼續使用之前閉包中的值。

      舉個例子:

      function Counter () {
        const [count, setCount] = useState (0);
        
        useEffect (() => {
          const foo = count;  
          document.title = `You clicked ${foo} times`;  
        });
      } 
      

      在第一次渲染時,foo 被設置為 count 的初始值 0。當我們更新 count 為 1 時,effect 會重新運行,但這時它不會重新讀取 count。它會繼續使用閉包中存儲的 foo,其值仍為 0。
      所以 document.title 不會更新,它將保持 "You clicked 0 times"。這是因為我們沒有指定 deps 數組,所以 React 認為 effect 不依賴任何值。每次渲染后重新運行 effect 僅僅是為了刷新副作用。它并不會讀取最新的 props 或狀態值。在源碼中,effect 的重新運行如下所示:

      function useEffect (create, deps) {
        //...
        if (didRender) {
          // 重新運行 effect, 但不會重新計算值
          create (); 
        }
        
        if (depsChanged) recompute ();  
      }
      

      所以當你不指定 deps 時,didRender 值會在每次渲染后變為 true,從而重新運行 effect。但是,由于 depsChanged 總是 false,所以 recompute 函數不會被調用。effect 在重新運行時只會調用 create 函數,但不會重新讀取值或更新閉包中的存儲值。所以它會繼續使用閉包中的 “舊值”,而不是最新的 props 和狀態。這就是當不指定依賴項 deps 時,useEffect 作用在重新運行時如何繼續使用閉包中的 “舊值” 而非最新值的實現機制。

      在 useEffect 源碼中,“舊值” 是通過 useRef hook 保存的。useRef 返回一個可變的 ref 對象,其 .current 屬性被用于存儲任何值,這個值在組件的整個生命周期中持續存在。所以,useEffect 使用 useRef 來保存第一次執行時讀取的 props 和狀態的值,這些就是所謂的 “舊值”。

      useEffect 的簡化實現如下:

      function useEffect (create, deps) {
        const storedValue = useRef (null);  // 使用 useRef 保存舊值
        
        function recompute () {
          const newValue = create ();   // 重新運行,讀取最新值
          storedValue.current = newValue;  // 更新舊值
        }
        
        if (didRender) {
          create ();  // 重新運行,使用舊值 storedValue.current
        } 
        
        if (depsChanged) recompute ();  // 如果 deps 變化,重新計算新值 
      }
      
      • 在初始渲染時,會運行 create 函數,讀取一些值并將其賦值給 storedValue。這些就是 “舊值”。
      • 如果沒有指定依賴項,didRender 將在每次渲染后變為 true,重新運行 create 函數,但這時仍使用存儲在 storedValue 中的 “舊值”。
      • 如果指定了依賴項 deps,且 deps 發生變化,recompute 函數會重新運行 create,讀取最新的值,并將其更新到 storedValue 中,覆蓋 “舊值”。
      • 如果依賴項 deps 沒有變化,什么也不會發生 ——storedValue 中的 “舊值” 會繼續被使用。

      所以,useRef hook 被用來在 effect 的多次執行之間保存 props 和狀態的 “舊值”。每當依賴關系無變化時,這些 “舊值” 會繼續被使用。通過指定依賴項,你可以確保在值變化時重新運行 effect, 并使用最新的 props 和狀態值更新存儲的 “舊值”。這就是 useEffect 源碼中 “舊值” 如何被保存及使用的實現機制。理解它對于掌握 useEffect 的工作原理非常重要。

      create 函數可以讀取 storedValue 的值,因為:

      1. storedValue 是在 effect 函數內聲明的。
      2. create 函數也是在 effect 函數內定義的,所以它可以訪問 effect 作用域中的變量,包括 storedValue。
      3. 這是閉包的結果,create 函數會捕獲 surrounding scope 的變量,使其值得以在多次調用之間保持。

      舉個例子:

      function useEffect (create, deps) {
        const storedValue = useRef (null);
        
        function effect () {
          const create = () => {
            console.log (storedValue.current);  // 可以訪問 storedValue
          }
        }
        
        //...
      }
      

      這里,create 函數被定義在 effect 函數內部。所以它可以訪問 effect 作用域中的變量,包括 storedValue。當 create 函數在后續調用中運行時,它會繼續使用創建時捕獲的 storedValue 變量。這是閉包的結果 —— 即使 effect 函數完成執行,create 函數所捕獲的變量也會被保留在內存中,供后續調用使用。

      總結一下:

      1. create 和 storedValue 都是在 effect 內聲明的,所以 create 可以訪問 storedValue。
      2. create 函數捕獲了 surrounding scope 的變量,使得這些變量在函數調用之間保持其值。這就是閉包。
      3. 所以,每當 create 被調用時,它都可以訪問之前聲明的 storedValue,并讀取其當前值。
      4. 這就是 create 如何可以在多次調用之間共享并訪問同一個 storedValue 的機制。

      這一點對理解 useEffect 的工作原理很重要。 effect 中聲明的變量和函數都會被捕獲在閉包中,并在多次 effect 執行之間共享。理解了這一點,useEffect 中 “舊值” 的保存和讀取機制也就很清楚了。

      在 useEffect 源碼中,effect 函數會在以下情況被調用:

      1. 在組件初始渲染時。此時它會執行 effect,讀取 props 和狀態的值,并將其存儲以供后續執行使用。
      2. 如果你指定了依賴項 deps,且 deps 中的任何值發生變化時。這時它會重新執行 effect,讀取最新的 props 和狀態值,并更新存儲的 “舊值”。
      3. 如果你不指定依賴項 deps,則在每次渲染后都會調用 effect。這時它會繼續使用存儲的 “舊值”。

      大致的 useEffect 實現如下:

      function useEffect (create, deps) {
        const effect = () => {
          const newValue = create ();  // 讀取最新值
          storedValue.current = newValue;  // 更新舊值
        }
        
        if (!deps) {
          didRender = true;  // 沒有依賴項,每次渲染后運行
        }
        
        if (didRender) effect ();   // 運行 effect
        
        if (deps && depsChanged) {  
          effect ();     // 如果有依賴項且變化了,運行 effect
        }
      }
      

      根據是否指定了依賴項 deps 及其是否發生變化,effect 會在以下情況被調用:

      1. 第一次渲染。此時會調用 effect,將 create 函數讀取的值存儲為 “舊值”。
      2. 如果指定了 deps 但未變化,什么也不會發生。繼續使用存儲的 “舊值”。
      3. 如果指定了 deps 且其發生變化,會調用 effect,通過 create 函數讀取最新的值,并更新存儲的 “舊值”。
      4. 如果不指定 deps,didRender 會在每次渲染后變為 true,從而調用 effect。但這時會繼續使用存儲的 “舊值”。

      effect 函數的調用與是否指定依賴項 deps 及 deps 是否發生變化直接相關。理解 effect 根據這些條件的不同調用方式,是理解 useEffect 的關鍵。useEffect 會在合適的時機調用 effect,以執行必要的副作用操作,同時確保你在 effect 中總是使用最新的 props 和狀態值。這就是 useEffect 的強大之處。

      useEffect 大致實現
      function useEffect (create, deps) {
        const effect = () => {
          const newValue = create ();  // Re-run create and get new value
          storedValue.current = newValue;  // Update stored value
        };
      
        const storedValue = useRef (null);
      
        const [depsChanged, setDepsChanged] = useState (false);
      
        if (didRenderRef.current && deps === undefined) {
          throw new Error ('Must either specify deps or no deps');
        }
      
        const prevDeps = useRef (deps);
        const didRenderRef = useRef (false);
      
        if (depsChanged || !prevDeps.current) {
          prevDeps.current = deps;  // Update prevDeps ref 
          didRenderRef.current = true;
        }
      
        useLayoutEffect (() => {
          if (didRenderRef.current && !depsChanged && prevDeps.current !== deps) {
            setDepsChanged (true);  // Trigger re-run of effect
          }
        });
      
        // Call the effect
        useLayoutEffect (() => {
          effect ();
        });
      
        // Re-run effect if deps change 
        useLayoutEffect (() => {
          if (depsChanged && didRenderRef.current) {
            effect ();
            didRenderRef.current = false;
            setDepsChanged (false);
          }
        }, deps);
      
        // Always re-run on mount
        useLayoutEffect (effect, []); 
      }
      

      useMemo

      useMemo 用于優化組件的渲染性能。它會在依賴項變化時重新計算 memoized 值,并且只在依賴項變化時重新渲染組件。你應該在以下情況使用 useMemo:

      1. 昂貴的計算:如果你有一個復雜的計算,它應該只在某些依賴項變化時重新運行,那么 useMemo 非常有用。它會記住最后計算的值,并僅在依賴項變化時重新計算。
      2. 避免不必要的渲染:如果你有一個組件,它在重新渲染時執行昂貴的 DOM 操作,那么你應該通過 useMemo 來優化它,使其只在依賴項變化時重新渲染。
      3. 依賴項變化時才重新計算值:如果你想基于 props 的某些值來計算一些數據,并且你只想在依賴 props 值變化時重新計算該數據。

      在 React 函數組件中,每當組件重新渲染時,其函數體都會被執行。這意味著任何計算的數據或渲染的元素都會重新計算和重新創建。這通常沒什么問題,但如果計算或渲染代價高昂,它可能會造成性能問題。

      舉個例子:

      const expensiveComputation = (a, b) => {
        // 做一些昂貴的計算...
        return result;
      }
      
      function MyComponent () {
        const [a, setA] = useState (1);
        const [b, setB] = useState (1);
      
        const result = expensiveComputation (a, b);
        //...
      }
      

      在這里,每當組件重新渲染時,expensiveComputation 都會被調用,即使 a 和 b 沒有變化。使用 useMemo 可以解決這個問題:

      function MyComponent () {
        const [a, setA] = useState (1);
        const [b, setB] = useState (1);
        const result = useMemo (() => expensiveComputation (a, b), [a, b]);
      
        //...
      }
      

      現在,result 只會在 a 或 b 變化時重新計算。所以,useMemo 的主要目的就是為了避免 React 函數組件不必要的重復計算,提高組件的性能。

      useMemo 的實現

      useMemo 的實現比較簡單,它基本上是 useEffect 的一個特例。

      function useMemo (nextCreate, deps) {
        currentlyRenderingMemo++;
      
        const create = useRef (nextCreate);
        const depsRef = useRef (deps);
        
        function recompute () {
          currentlyRenderingMemo++;
          const memoizedValue = create.current ();
          memoized.current = memoizedValue;
          currentlyRenderingMemo--;
        }
        
        if (deps.current !== deps) {
          deps.current = deps; 
          recompute ();
        }
      
        const memoized = useRef (null);
        if (currentlyRenderingMemo === 0) {
          recompute ();
        } 
        
        return memoized.current;
      }
      

      它做了以下幾件事:

      1. 當前渲染的 useMemo 數量加 1。這是為了避免在嵌套的 useMemo 調用中重復運行 effects。
      2. 用 useRef 創建對 create 函數和 deps 數組的引用。
      3. 定義 recompute 函數來調用 create 函數并更新 memoized 值。
      4. 如果 deps 變化了,調用 recompute 來重新計算 memoized 值。
      5. 如果這是第一個 useMemo 調用,調用 recompute 來計算初始 memoized 值。
      6. 返回 memoized 值。
      7. 在組件卸載時,自動清空 refs,相當于運行過清理函數。

      所以本質上,它只在依賴項變化時重新運行 create 函數,并記住最后的值,這與 useEffect 有些相似。但 useMemo 專注于記憶化值,而不產生任何副作用。這就是 React 中 useMemo 的簡單實現原理。它通過跟蹤依賴項和緩存上次計算的值來優化組件渲染性能。

      useCallback

      useCallback 與 useMemo 類似,它也是用于優化性能的。但是它用于記憶化函數,而不是值。useCallback 會返回一個 memoized 回調函數,它可以確保函數身份在多次渲染之間保持不變,僅在某個依賴項變化時才會更新,這可以用于避免在每次渲染時都創建新的函數實例。所以,當你有一個會在多次渲染之間保持不變的函數時,使用 useCallback 是一個很好的優化手段。

      舉個例子,當你有一個函數作為事件處理程序時,它通常在創建后就不會改變。但是,如果你直接在渲染方法中定義這個函數,它會在每次渲染時重新創建。

      function MyComponent () {
        const [count, setCount] = useState (1);
        
        function handleClick () {
          setCount (c => c + 1);
        }
        
        return <button onClick={handleClick}>Increment</button>
      }
      

      這里,handleClick 函數在每次渲染時都會重新定義。雖然它的邏輯在多次渲染之間沒有變化。

      使用 useCallback 優化這段代碼:

      function MyComponent () {
        const [count, setCount] = useState (1);
      
        const handleClick = useCallback (() => {
          setCount (c => c + 1);
        }, []);  // 依賴項 [] 表示僅在第一次渲染時創建
        
        return <button onClick={handleClick}>Increment</button>
      }
      

      現在,handleClick 只會在第一次渲染時創建。在隨后的渲染中,它都指向同一個函數實例。這可以避免在每次渲染時創建新的事件處理程序,從而優化組件的性能。

      總結一下: useCallback 的主要作用是:

      1. 記憶化函數實例,避免在每次渲染時創建新的函數。
      2. 當函數作為 props 傳遞給子組件時,可以讓子組件避免不必要的重新渲染。

      與 useMemo 類似,你應該在依賴項變化時才更新回調函數。否則,它就沒有意義了。總之,useCallback 主要用于性能優化,通過記憶化函數實例來避免不必要的重新創建和重新渲染。

      useCallback 的實現

      useCallback 的實現也比較簡單。它基本上就是用 useMemo 來記憶化一個函數。

      function useCallback (callback, deps) {
        return useMemo (() => callback, deps);
      }
      

      它直接調用 useMemo,傳入 callback 函數和 deps 數組。

      所以,useCallback 的工作原理是:

      1. 在第一次渲染時,調用 callback 函數并記住結果。
      2. 在后續渲染中,如果 deps 沒有變化,直接返回上次記住的函數。
      3. 如果 deps 變化了,再次調用 callback 并記住新結果。
      4. 在組件卸載時,自動清理 useMemo 的副作用。

      所以本質上,它就是把函數當作 useMemo 的創建函數來調用,并根據依賴項決定是否需要重新創建函數實例。這與事件處理程序的例子非常吻合。

      舉個具體例子:

      function useCallback (callback, [a, b]) {
        return useMemo (() => {
          callback ()     // 只在第一次渲染時調用
        }, [a, b])      // 如果 a 或 b 變化時重新調用 callback
      }
      

      那么第一次渲染時會立即調用 callback,并記住結果。如果后續 a 或 b 變化,callback 會再次被調用,并更新記憶的值。如果 a 和 b 保持不變,直接返回上次記住的函數實例。這就是 useCallback 的簡單實現原理。它通過將函數實例記憶化來確保函數身份在多次渲染之間保持一致,從而優化性能。綜上,useCallback 的實現是基于 useMemo 的。它利用 useMemo 的記憶化特性來記憶化函數,以此來提高組件渲染性能。

      posted @ 2023-05-08 09:46  guangzan  閱讀(4000)  評論(2)    收藏  舉報
      主站蜘蛛池模板: 亚洲中文字幕无码爆乳APP| 高清无码爆乳潮喷在线观看| 亚洲经典av一区二区| 成人做受120秒试看试看视频| 久久精品波多野结衣| 国产精品永久久久久久久久久| 亚洲精品美女一区二区| 又粗又大又硬又长又爽| 精品精品亚洲高清a毛片| 亚洲欧洲精品成人久久曰| 久久人人97超碰爱香蕉| 亚洲精品动漫免费二区| 色综合五月伊人六月丁香| 国产99视频精品免费专区| 狠狠爱俺也去去就色| 成人久久精品国产亚洲av| 成人网站网址导航| 国产精品久久久久久人妻精品动漫| 精品一区精品二区制服| 国产精品综合一区二区三区| 大陆一级毛片免费播放| h无码精品动漫在线观看| 国产精品不卡一区二区在线| 国产亚洲欧洲AⅤ综合一区| 亚洲精品www久久久久久| 亚洲区综合中文字幕日日| 性姿势真人免费视频放| 99久久99久久久精品久久| 日韩不卡一区二区在线观看| 免费国产好深啊好涨好硬视频| 久久91精品牛牛| 美女一区二区三区亚洲麻豆| 黄色三级亚洲男人的天堂| 国产精一区二区黑人巨大| 亚洲色拍拍噜噜噜最新网站| 久久久精品波多野结衣av | 日本一区二区中文字幕久久| 亚洲精品日韩在线观看| 国产AV福利第一精品| 国产粉嫩区一区二区三区| 亚洲AV无码东方伊甸园|