深入瞭解 useMemo 和 useCallback

語言: CN / TW / HK

深入瞭解 useMemo 和 useCallback

許多人對 useMemouseCallback 的理解和使用都不太正確,他們都對這兩個鉤子感到困惑。本文中的目標就是要澄清所有這些困惑。本文將學習它們是做什麼的,為什麼它們是有用的,以及如何最大限度地利用它們。

本文的目的是幫助初學者 or 中級 React 開發人員更好地使用 React。如果你剛剛開始使用 React,你可能會希望將這篇文章收藏起來,幾周後再回來看它!

1. 基本思想

我們從 useMemo 開始。 useMemo 的基本思想是它允許我們在渲染之間“記住”計算值。這個定義需要一些解釋,我們先來解決這個問題。

React 做的主要事情是保持UI與應用程式狀態同步。它用來做這件事的工具叫做“re-render”。基於當前應用程式狀態,每次重新呈現都是應用程式UI在給定時刻應該是什麼樣子的快照。我們可以把它想象成一堆照片,每一張照片都記錄了給定每個狀態變數的特定值時事物的樣子。

每次“re-render”都會根據當前狀態在腦海中生成 DOM 應該是什麼樣子的影象。但實際上它是一堆JS物件,被稱為“ 「virtual DOM」 ”。

我們不直接告訴 React 需要更改哪些 DOM 節點。相反,我們根據當前狀態告訴React UI應該是什麼樣子。通過重新渲染,React 建立一個新的快照,它可以通過比較快照找出需要更改的內容,就像玩“尋找差異”遊戲一樣。

React 在開箱即用時進行了大量優化,所以通常情況下,重新渲染不是什麼大問題。但是,在某些情況下,建立這些快照確實需要一些時間。這可能會導致效能問題,比如 UI 在使用者執行操作後更新不夠快。

useMemouseCallback 是用來幫助我們優化重渲染的工具。他們通過兩種方式做到這一點:

  1. 減少在給定渲染中需要完成的工作量。

  2. 減少元件需要重新呈現的次數。

讓我們通過下面的栗子來理解它們吧。

2. 示例1:大量的計算

假設我們正在構建一個工具來幫助使用者查詢 0 到 selectedNum 之間的所有素數,其中 selectedNum 是使用者提供的值。 「質數是隻能被1和自身整除的數,比如17。」 下面是一個可能的實現:

import React from 'react';

function App() {
// 儲存使用者所選號碼的狀態。
const [selectedNum, setSelectedNum] = React.useState(100);

// 計算從 0 到使用者選擇的數字 selectedNum 之間的所有素數
const allPrimes = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
allPrimes.push(counter);
}
}

return (
<>
<form>
<label htmlFor="num">Your number:</label>
<input
type="number"
value={selectedNum}
onChange={(event) =>
{
// 為了防止太大,我們將最大值設定在10萬
let num = Math.min(100_000, Number(event.target.value));

setSelectedNum(num);
}}
/>
</form>
<p>
There are {allPrimes.length} prime(s) between 1 and {selectedNum}:{' '}
<span className="prime-list">{allPrimes.join(', ')}</span>
</p>
</>

);
}

// 計算給定數字是否是素數的 Helper 函式
function isPrime(n) {
const max = Math.ceil(Math.sqrt(n));
if (n === 2) {
return true;
}
for (let counter = 2; counter <= max; counter++) {
if (n % counter === 0) {
return false;
}
}
return true;
}

export default App;

我們有一個狀態,一個叫做 selectedNum 的數字。使用 for 迴圈,我們手動計算 0 到 selectedNum 之間的所有素數。我們呈現一個受控制的數字輸入,因此使用者可以更改 selectedNum 。我們向用戶顯示我們計算的所有質數。

這段程式碼需要大量的計算。如果使用者選擇一個較大的 selectedNum ,我們將需要遍歷成千上萬個數字,檢查是否每個數字都是素數。而且,雖然有比我上面使用的更有效的質數檢查演算法,但它總是需要大量的計算。

有時我們確實需要執行這個計算,比如當用戶選擇一個新的 selectedNum 時。但是我們可能會遇到一些效能問題,如果我們在不需要做的時候無償地做這項工作。

例如,讓我們假設我們的例子還包含一個數字時鐘:

import React from 'react';
import format from 'date-fns/format';

function App() {
const [selectedNum, setSelectedNum] = React.useState(100);

// time 是一個每秒改變一次的狀態變數,因此它總是與當前時間同步。
const time = useTime();

// 計算所有質數(與前面的示例相同)
const allPrimes = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
allPrimes.push(counter);
}
}

return (
<>
<p className="clock">
{format(time, 'hh:mm:ss a')}
</p>
<form>
<label htmlFor="num">Your number:</label>
<input
type="number"
value={selectedNum}
onChange={(event) =>
{
// 為了防止太大,我們將最大值設定在10萬
let num = Math.min(100_000, Number(event.target.value));
setSelectedNum(num);
}}
/>
</form>
<p>
There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
{' '}
<span className="prime-list">
{allPrimes.join(', ')}
</span>
</p>
</>

);
}

function useTime() {
const [time, setTime] = React.useState(new Date());

React.useEffect(() => {
const intervalId = window.setInterval(() => {
setTime(new Date());
}, 1000);

return () => {
window.clearInterval(intervalId);
}
}, []);

return time;
}

function isPrime(n){
const max = Math.ceil(Math.sqrt(n));
if (n === 2) {
return true;
}
for (let counter = 2; counter <= max; counter++) {
if (n % counter === 0) {
return false;
}
}
return true;
}

export default App;

我們的應用程式現在有兩個狀態, selectedNumtime 。時間變數每秒更新一次,以反映當前時間,該值用於呈現右上角的數字時鐘。

問題在於: 「每當這些狀態變數發生變化時,我們就會重新執行那些昂貴的質數計算。因為時間每秒改變一次,這意味著我們不斷地重新生成質數列表,即使使用者選擇的數字沒有改變!!!」

在 JavaScript 中,我們只有一個主執行緒,我們通過一遍又一遍地執行這段程式碼讓它非常繁忙,每一秒。這意味著當用戶嘗試做其他事情時,應用程式可能會感到遲緩,特別是在低端裝置上。

但如果我們可以“跳過”這些計算呢?如果我們已經有了一個給定數字的質數列表,為什麼不重用這個值而不是每次都從頭計算呢?這正是 useMemo 允許我們做的。它看起來是這樣的:

const allPrimes = React.useMemo(() => {
const result = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
result.push(counter);
}
}
return result;
}, [selectedNum]);

useMemo 有兩個引數:

  1. 要執行的工作塊,封裝在函式中

  2. 依賴項列表

在掛載期間,當這個元件第一次呈現時,React 將呼叫這個函式來執行所有的邏輯,計算所有的質數。無論我們從這個函式返回什麼,都被賦值給 allPrimes 變數。

然而,對於每一個後續渲染,React 都要做出選擇。

  1. 再次呼叫函式,重新計算值

  2. 重用它上次執行此工作時已經擁有的資料。

為了做出選擇,React 檢視提供的依賴項列表。對於之前的渲染有任何改變嗎?如果是,React 將重新執行提供的函式,以計算一個新的值。否則,它將跳過所有這些工作並重用之前計算的值。

useMemo 本質上類似於快取,依賴項是快取失效策略。在本例中,我們實際上是在說“只有當 selectedNum 發生變化時才重新計算質數列表”。當元件由於其他原因重新呈現時(例如。當時間狀態變數發生變化時), useMemo 忽略函式並傳遞快取的值。

這通常被稱為記憶,這就是為什麼這個鉤子被稱為 useMemo 。下面是這個解決方案的實時版本:

import React from 'react';
import format from 'date-fns/format';

function App() {
const [selectedNum, setSelectedNum] = React.useState(100);
const time = useTime();

const allPrimes = React.useMemo(() => {
const result = [];

for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
result.push(counter);
}
}

return result;
}, [selectedNum]);

return (
<>
<p className="clock">
{format(time, 'hh:mm:ss a')}
</p>
<form>
<label htmlFor="num">Your number:</label>
<input
type="number"
value={selectedNum}
onChange={(event) =>
{
// 為了防止太大,我們將最大值設定在10萬
let num = Math.min(100_000, Number(event.target.value));

setSelectedNum(num);
}}
/>
</form>
<p>
There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
{' '}
<span className="prime-list">
{allPrimes.join(', ')}
</span>
</p>
</>

);
}

function useTime() {
const [time, setTime] = React.useState(new Date());

React.useEffect(() => {
const intervalId = window.setInterval(() => {
setTime(new Date());
}, 1000);

return () => {
window.clearInterval(intervalId);
}
}, []);

return time;
}

function isPrime(n){
const max = Math.ceil(Math.sqrt(n));
if (n === 2) {
return true;
}
for (let counter = 2; counter <= max; counter++) {
if (n % counter === 0) {
return false;
}
}
return true;
}

因此, useMemo 鉤子確實可以幫助我們避免這裡不必要的計算。但它真的是這裡的最佳解決方案嗎?通常,我們可以通過重組應用程式中的內容來避免對 useMemo 的需求。我們可以這樣做:

  1. PrimeCalculator.js
import React from 'react';

function PrimeCalculator() {
const [selectedNum, setSelectedNum] = React.useState(100);

const allPrimes = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
allPrimes.push(counter);
}
}

return (
<>
<form>
<label htmlFor="num">Your number:</label>
<input
type="number"
value={selectedNum}
onChange={(event) =>
{
// 為了防止太大,我們將最大值設定在10萬
let num = Math.min(100_000, Number(event.target.value));

setSelectedNum(num);
}}
/>
</form>
<p>
There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
{' '}
<span className="prime-list">
{allPrimes.join(', ')}
</span>
</p>
</>

);
}

function isPrime(n){
const max = Math.ceil(Math.sqrt(n));
if (n === 2) {
return true;
}
for (let counter = 2; counter <= max; counter++) {
if (n % counter === 0) {
return false;
}
}
return true;
}

export default PrimeCalculator;
  1. Clock.js
import React from 'react';
import format from 'date-fns/format';

function Clock() {
const time = useTime();

return (
<p className="clock">
{format(time, 'hh:mm:ss a')}
</p>

);
}

function useTime() {
const [time, setTime] = React.useState(new Date());

React.useEffect(() => {
const intervalId = window.setInterval(() => {
setTime(new Date());
}, 1000);

return () => {
window.clearInterval(intervalId);
}
}, []);

return time;
}

export default Clock;
  1. App.js
import React from 'react';

import Clock from './Clock';
import PrimeCalculator from './PrimeCalculator';

function App() {
return (
<>
<Clock />
<PrimeCalculator />
</>

);
}

export default App;

我提取了兩個新元件, ClockPrimeCalculator 。通過從 App 分支,這兩個元件各自管理自己的狀態。一個元件中的重新渲染不會影響另一個元件。

或許你聽到很多關於提升狀態的說法,但有時,更好的方法是將狀態向下推。每個元件應該有一個單獨的職責,在上面的例子中, App 正在做兩件完全不相關的事情。

現在,這並不總是一個選擇。在一個大型的現實應用中,有許多狀態需要向上提升,而不能向下推。對於這種情況,我還有另一個妙計。讓我們看一個例子。假設我們需要將 time 變數提升到 PrimeCalculator 之上:

  1. PrimeCalculator.js
import React from 'react';

function PrimeCalculator() {
const [selectedNum, setSelectedNum] = React.useState(100);

const allPrimes = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
allPrimes.push(counter);
}
}

return (
<>
<form>
<label htmlFor="num">Your number:</label>
<input
type="number"
value={selectedNum}
onChange={(event) =>
{
// 為了防止太大,我們將最大值設定在10萬
let num = Math.min(100_000, Number(event.target.value));

setSelectedNum(num);
}}
/>
</form>
<p>
There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
{' '}
<span className="prime-list">
{allPrimes.join(', ')}
</span>
</p>
</>

);
}

function isPrime(n){
const max = Math.ceil(Math.sqrt(n));
if (n === 2) {
return true;
}
for (let counter = 2; counter <= max; counter++) {
if (n % counter === 0) {
return false;
}
}
return true;
}

export default PrimeCalculator;
  1. Clock.js
import React from 'react';
import format from 'date-fns/format';

function Clock({ time }) {
return (
<p className="clock">
{format(time, 'hh:mm:ss a')}
</p>

);
}

export default Clock;
  1. App.js
import React from 'react';
import { getHours } from 'date-fns';

import Clock from './Clock';
import PrimeCalculator from './PrimeCalculator';

// 將我們的PrimeCalculator轉換為一個純元件
const PurePrimeCalculator = React.memo(PrimeCalculator);

function App() {
const time = useTime();

// 根據一天中的時間選擇一個合適的背景色
const backgroundColor = getBackgroundColorFromTime(time);

return (
<div style={{ backgroundColor }}>
<Clock time={time} />
<PurePrimeCalculator />
</div>

);
}

const getBackgroundColorFromTime = (time) => {
const hours = getHours(time);

if (hours < 12) {
// 早晨用的淡黃色
return 'hsl(50deg 100% 90%)';
} else if (hours < 18) {
// 下午暗淡的藍色
return 'hsl(220deg 60% 92%)'
} else {
// 夜晚的深藍色
return 'hsl(220deg 100% 80%)';
}
}

function useTime() {
const [time, setTime] = React.useState(new Date());

React.useEffect(() => {
const intervalId = window.setInterval(() => {
setTime(new Date());
}, 1000);

return () => {
window.clearInterval(intervalId);
}
}, []);

return time;
}

export default App;

React.memo 包在元件周圍,保護它免受不相關的更新。 PurePrimeCalculator 只有在接收到新資料或內部狀態發生變化時才會重新呈現。這就是所謂的純元件。本質上,我們告訴 React 這個元件將總是在相同的輸入條件下產生相同的輸出,我們可以跳過沒有任何改變的重新呈現。

在上面的例子中,我應用了 React.memo 到匯入的 PrimeCalculator 元件。事實上,我選擇了這樣的結構,以便所有內容都在同一個檔案中可見,以便更容易理解。在實踐中,使用 React.memo 元件匯出,如下所示:

// PrimeCalculator.js
function PrimeCalculator() {
/* 這裡的元件內容 */
}
export default React.memo(PrimeCalculator);

我們的 PrimeCalculator 元件現在將始終是純的,當我們要使用它時,不需要對它進行修補。

這裡有一個視角轉換:之前,我們在記憶一個特定計算的結果,計算質數。然而,在本例中,我記住了整個元件。無論哪種方式,只有當用戶選擇一個新的 selectedNum 時,昂貴的計算才會重新執行。但我們優化的是父元件,而不是特定的慢程式碼行。

我並不是說一種方法比另一種更好;每種工具在工具箱中都有自己的位置。但在這個特定的情況下,我更喜歡這種方法。現在,如果您曾經嘗試在現實世界的設定中使用純元件,您可能會注意到一些特殊的東西:純元件經常重新渲染相當多,即使看起來沒有任何變化!這很好地將我們引入了 useMemo 解決的第二個問題。

3. 示例2:保留引用

在下面的示例中,我建立了一個 Boxes 元件。它展示了一組彩色的盒子,用於某種裝飾目的。我還有一個不相關的狀態:使用者名稱。

  1. Boxes.js
import React from 'react';

function Boxes({ boxes }) {
return (
<div className="boxes-wrapper">
{boxes.map((boxStyles, index) => (
<div
key={index}
className="box"
style={boxStyles}
/>

))}
</div>

);
}

export default React.memo(Boxes);
  1. App.js
import React from 'react';

import Boxes from './Boxes';

function App() {
const [name, setName] = React.useState('');
const [boxWidth, setBoxWidth] = React.useState(1);

const id = React.useId();

// 嘗試改變這些值
const boxes = [
{ flex: boxWidth, background: 'hsl(345deg 100% 50%)' },
{ flex: 3, background: 'hsl(260deg 100% 40%)' },
{ flex: 1, background: 'hsl(50deg 100% 60%)' },
];

return (
<>
<Boxes boxes={boxes} />

<section>
<label htmlFor={`${id}-name`}>
Name:
</label>
<input
id={`${id}-name`}
type="text"
value={name}
onChange={(event) =>
{
setName(event.target.value);
}}
/>
<label htmlFor={`${id}-box-width`}>
First box width:
</label>
<input
id={`${id}-box-width`}
type="range"
min={1}
max={5}
step={0.01}
value={boxWidth}
onChange={(event) =>
{
setBoxWidth(Number(event.target.value));
}}
/>
</section>
</>

);
}

export default App;

由於在 boxes.js 中使用了 React.memo() 封裝預設匯出, Boxes 是一個純元件。這意味著它應該只在它的 props 改變時重新渲染。然而,每當使用者更改其名稱時, Boxes 也會重新呈現。

為什麼我們的 React.memo() 沒有保護我們?盒子元件只有1個 prop ,盒子,它看起來好像我們給它在每次渲染完全相同的資料。總是一樣的東西:一個紅盒子,一個紫色的寬盒子,一個黃色的盒子。我們確實有一個影響 boxes 陣列的 boxWidth 狀態變數,但我們沒有更改它!

問題在於:每次 React 重新渲染時,我們都會生成一個全新的陣列。它們在值上是相等的,但在參照物上是不同的。我想如果我們先不談 React,只談普通的 JavaScript,會很有幫助。讓我們來看一個類似的情況:

function getNumbers() {
return [1, 2, 3];
}
const firstResult = getNumbers();
const secondResult = getNumbers();
console.log(firstResult === secondResult);

你怎麼看? firstResult 是否等於 secondResult ?從某種意義上說,的確如此。兩個變數都具有相同的結構 [1,2,3] 。但這不是 === 運算子實際檢查的內容。相反, === 檢查兩個表示式是否相同。我們已經建立了兩個不同的陣列。它們可能包含相同的內容,但它們不是同一個陣列。

每次呼叫 getNumbers 函式時,我們都會建立一個全新的陣列,它是儲存在計算機記憶體中的一個不同的東西。如果我們多次呼叫它,我們將在記憶體中儲存該陣列的多個副本。注意,簡單的資料型別——比如 「字串」「數字」「布林值」 ——可以按值進行比較。但是當涉及到 「陣列」「物件」 時,它們只能通過 「引用」 進行比較。

讓我們回到 React:我們的 Boxes React元件也是一個 JavaScript 函式。當我們渲染它時,我們呼叫那個函式:

// 每次渲染這個元件時,我們呼叫這個函式…
function App() {
// 最後創造了一個全新的陣列
const boxes = [
{ flex: boxWidth, background: 'hsl(345deg 100% 50%)' },
{ flex: 3, background: 'hsl(260deg 100% 40%)' },
{ flex: 1, background: 'hsl(50deg 100% 60%)' },
];
// .然後將其作為 prop 傳遞給該元件!
return (
<Boxes boxes={boxes} />
);
}

當名稱狀態改變時,我們的 App 元件將重新呈現,這將重新執行所有的程式碼。我們構造一個全新的 boxes 陣列,並將其傳遞給我們的 Boxes 元件。從而導致盒子重新渲染,因為我們給了它一個全新的陣列。盒子陣列的結構在渲染之間沒有改變,但這無關緊要。React 所知道的是,箱子 prop 已經收到了一個新建立的,從未見過的陣列。要解決這個問題,我們可以使用 useMemo hook:

const boxes = React.useMemo(() => {
return [
{ flex: boxWidth, background: 'hsl(345deg 100% 50%)' },
{ flex: 3, background: 'hsl(260deg 100% 40%)' },
{ flex: 1, background: 'hsl(50deg 100% 60%)' },
];
}, [boxWidth]);

與我們之前看到的質數例子不同,這裡我們不擔心計算成本很高的計算。我們的唯一目標是 「保留對特定陣列的引用」 。我們將 boxWidth 列為一個依賴項,因為我們確實希望在使用者調整紅色框的寬度時重新呈現 Boxes 元件。然而,在 useMemo 中,我們重用了之前建立的 boxes 陣列。

通過在多個渲染中保留相同的引用,我們允許純元件按我們希望的方式工作,忽略不影響 UI 的渲染。

4. useCallback

前面我們瞭解了 useMemo 。那 useCallback 呢?這是一個簡短的版本: 「這是完全相同的事情,但用於函式而不是陣列/物件」 。與陣列和物件類似,函式是根據引用比較的,而不是根據值:

const functionOne = function() {
return 5;
};
const functionTwo = function() {
return 5;
};
console.log(functionOne === functionTwo); // false

這意味著,如果我們在元件中定義一個函式,它將在每次渲染時重新生成,每次生成一個相同但唯一的函式。讓我們看一個例子:

  1. MegaBoost.js
import React from 'react';

function MegaBoost({ handleClick }) {
console.log('Render MegaBoost');

return (
<button
className="mega-boost-button"
onClick={handleClick}
>

MEGA BOOST!
</button>

);
}

export default React.memo(MegaBoost);
  1. App.js
import React from 'react';

import MegaBoost from './MegaBoost';

function App() {
const [count, setCount] = React.useState(0);

function handleMegaBoost() {
setCount((currentValue) => currentValue + 1234);
}

return (
<>
Count: {count}
<button
onClick={() =>
{
setCount(count + 1)
}}
>
Click me!
</button>
<MegaBoost handleClick={handleMegaBoost} />
</>

);
}

export default App;

這個栗子描述了一個典型的計數器應用程式,但有一個特殊的“Mega Boost”按鈕。這個按鈕大大增加了計數,以防你很匆忙,不想多次點選標準按鈕。

多虧了 React.memo , MegaBoost 元件是一個純元件。它不依賴於計數,但每當計數改變時它就會重新呈現!就像我們看到的盒子陣列,這裡的問題是我們在每個渲染上生成一個全新的函式。如果我們渲染 3 次,我們將建立 3 個單獨的 handleMegaBoost 函式,突破 React.memo 的保護。利用我們對 useMemo 的瞭解,我們可以像這樣解決問題:

const handleMegaBoost = React.useMemo(() => {
return function() {
setCount((currentValue) => currentValue + 1234);
}
}, []);

我們返回的不是一個數組,而是一個函式。然後將此函式儲存在 handleMegaBoost 變數中。這很有效,但還有更好的方法:

const handleMegaBoost = React.useCallback(() => {
setCount((currentValue) => currentValue + 1234);
}, []);

useCallback 的作用與 useMemo 相同,但它是專門為函式構建的。我們直接給它一個函式,它記住那個函式,在渲染之間進行執行緒處理。換句話說,這兩個表達有相同的效果:

React.useCallback(function helloWorld(){}, []);
// 在功能上等價於
React.useMemo(() => function helloWorld(){}, []);

useCallback 是語法糖。它的存在純粹是為了讓我們在記憶回撥函式時更加方便。

5. 什麼時候使用這些 hook

好了,我們已經看到了 useMemouseCallback 如何允許我們跨多個渲染執行緒引用重用複雜的計算或避免破壞純元件。問題是:我們應該多經常使用它?

在我個人看來,將每個物件/陣列/函式包裝在這些鉤子中是浪費時間。在大多數情況下,好處是可以忽略不計的;React 是高度優化的,重新渲染通常不像我們通常認為的那樣緩慢或昂貴!

使用這些鉤子的最佳方式是響應問題。如果你注意到你的應用程式變得有點遲緩,你可以使用 React Profiler 來查詢緩慢的渲染。在某些情況下,可以通過重構應用程式來提高效能。在其他情況下, useMemouseCallback 可以幫助加快速度。

5.1 用於自定義 hook 內部

例如下面這個自定義 hook useToggle ,它的工作方式幾乎和 useState 完全一樣,但只能在 truefalse 之間切換狀態變數:

function App() {
const [isDarkMode, toggleDarkMode] = useToggle(false);

return (
<button onClick={toggleDarkMode}>
Toggle color theme
</button>

);
}

下面是如何定義這個自定義 hook 的:

function useToggle(initialValue) {
const [value, setValue] = React.useState(initialValue);

const toggle = React.useCallback(() => {
setValue(v => !v);
}, []);

return [value, toggle];
}

注意, toggle 函式是用 useCallback 記憶的。當我構建這樣的自定義可重用鉤子時,我希望使它們儘可能高效,因為我不知道將來會在哪裡使用它們。在95%的情況下,這可能是多餘的,但如果我使用這個鉤子30或40次,這很有可能有助於提高應用程式的效能。

5.2 在 context 提供者

當我們在具有 context 的應用程式之間共享資料時,通常會傳遞一個大物件作為 value 屬性。記住這個物件通常是個好主意:

const AuthContext = React.createContext({});

function AuthProvider({ user, status, forgotPwLink, children }){
const memoizedValue = React.useMemo(() => {
return {
user,
status,
forgotPwLink,
};
}, [user, status, forgotPwLink]);

return (
<AuthContext.Provider value={memoizedValue}>
{children}
</AuthContext.Provider>

);
}

為什麼這是有益的?可能有幾十個純元件使用這個上下文。如果沒有 useMemo ,如果 AuthProvider 的父元件碰巧重新渲染,那麼所有這些元件都將被迫重新渲染。

-   E N D   -

3 6 0 W 3 C E C M A T C 3 9 L e a d e r 注和加