React-memo-- 和 useMemo-- 的用法與區別
導語 | 本文翻譯自 Adebola Adeniran 在 LogRocket 論壇中關於 React.memo() 和 useMemo() 對比與用例分析。
在軟件開發中,我們通常癡迷於性能提升以及如何使我們的應用程序執行得更快,從而爲用戶提供更好的體驗。
Memoization 是優化性能的方法之一。在本文中,我們將探討它在 React 中的工作原理。
什麼是 memoization?
編者註解
在解釋這個概念之前,讓我們先來看一個簡單的斐波那契程序:
function fibonacci(n){
return (n < 2) ? n : fibonacci(n-1) + fibonacci(n-2);
}
顯然這個算法緩慢的令人絕望,因爲做了非常多的冗餘計算,這個時候 memoization 就可以派上用場了。
簡單來說,memoization 是一個過程,它允許我們緩存遞歸 / 昂貴的函數調用的值,以便下次使用相同的參數調用函數時,返回緩存的值而不必重新計算函數。
這確保了我們的應用程序運行得更快,因爲我們通過返回一個已經存儲在內存中的值來避免重新執行函數需要的時間。
爲什麼在 React 中使用 memoization?
在 React 函數組件中,當組件中的 props 發生變化時,默認情況下整個組件都會重新渲染。換句話說,如果組件中的任何值更新,整個組件將重新渲染,包括尚未更改其 values/props 的函數 / 組件。
讓我們看一個發生這種情況的簡單示例。我們將構建一個基本的應用程序,告訴用戶哪種酒最適合與它們選擇的奶酪搭配。
我們將從設置兩個組件開始。第一個組件將允許用戶選擇奶酪。然後它會顯示最適合該奶酪的酒的名稱。第二個組件將是第一個組件的子組件。在這個組件中,沒有任何變化。我們將使用這個組件來跟蹤 React 重新渲染的次數。
注意,本示例中使用的
classNames
來自 Tailwind CSS。
下面是我們的父組件:<ParentComponent />
。
// components/parent-component.js
import Counts from "./counts";
import Button from "./button";
import { useState, useEffect } from "react";
import constants from "../utils";
const { MOZARELLA, CHEDDAR, PARMESAN, CABERNET, CHARDONAY, MERLOT } = constants;
export default function ParentComponent() {
const [cheeseType, setCheeseType] = useState("");
const [wine, setWine] = useState("");
const whichWineGoesBest = () => {
switch (cheeseType) {
case MOZARELLA:
return setWine(CABERNET);
case CHEDDAR:
return setWine(CHARDONAY);
case PARMESAN:
return setWine(MERLOT);
default:
CHARDONAY;
}
};
useEffect(() => {
let mounted = true;
if (mounted) {
whichWineGoesBest();
}
return () => (mounted = false);
}, [cheeseType]);
return (
<div class>
<h3 class>
Without React.memo() or useMemo()
</h3>
<h1 class>
Select a cheese and we will tell you which wine goes best!
</h1>
<div class>
<Button text={MOZARELLA} onClick={() => setCheeseType(MOZARELLA)} />
<Button text={CHEDDAR} onClick={() => setCheeseType(CHEDDAR)} />
<Button text={PARMESAN} onClick={() => setCheeseType(PARMESAN)} />
</div>
{cheeseType && (
<p class>
For {cheeseType}, <span class>{wine}</span>{" "}
goes best.
</p>
)}
<Counts />
</div>
);
}
第二個組件是 <Counts />
組件,它跟蹤整個 <Parent Component />
組件重新渲染的次數。
// components/counts.js
import { useRef } from "react";
export default function Counts() {
const renderCount = useRef(0);
return (
<div class>
<p class>
Nothing has changed here but I've now rendered:{" "}
<span class>
{(renderCount.current++)} time(s)
</span>
</p>
</div>
);
}
下面的例子是我們點擊奶酪名字時的效果:
<ParentComponent />
中的 <Counts />
組件計算了因 <ParentComponent />
的更改而強制 <Counts />
組件重新渲染的次數。
目前,單擊奶酪名字將更新顯示下面的奶酪名字以及酒名。除了 <ParentComponent />
會重新渲染,<Counts />
組件也會重新渲染,即使其中的任何內容都沒有改變。
想象一下,有一個組件顯示數以千計的數據,每次用戶單擊一個按鈕時,該組件或樹中的每條數據都會在不需要更新時重新渲染。這就是 React.memo()
或 useMemo()
爲我們提供性能優化所必需的地方。
現在,讓我們探索 React.memo
以及 useMemo()
。之後我們將比較它們之間的差異,並瞭解何時應該使用一種而不是另一種。
什麼是 React.memo()?
React.memo()
隨 React v16.6 一起發佈。雖然類組件已經允許您使用 PureComponent 或 shouldComponentUpdate 來控制重新渲染,但 React 16.6 引入了對函數組件執行相同操作的能力。
React.memo()
是一個高階組件 (HOC**),**它接收一個組件 A 作爲參數並返回一個組件 B,如果組件 B 的 props(或其中的值)沒有改變,則組件 B 會阻止組件 A 重新渲染 。
我們將採用上面相同的示例,但在我們的 <Counts />
組件中使用 React.memo()
。我們需要做的就是用 React.memo()
包裹我們的 <Counts />
組件,如下所示:
import { useRef } from "react";
function Counts() {
const renderCount = useRef(0);
return (
<div class>
<p class>
Nothing has changed here but I've now rendered:{" "}
<span class>
{(renderCount.current ++)} time(s)
</span>
</p>
</div>
);
}
export default React.memo(Counts);
現在,當我們通過單擊選擇奶酪類型時,我們的 <Counts />
組件將不會重新渲染。
什麼是 useMemo()?
React.memo()
是一個 HOC,而**useMemo()**
是一個 React Hook。使用 useMemo()
,我們可以返回記憶值來避免函數的依賴項沒有改變的情況下重新渲染。
爲了在我們的代碼中使用 useMemo()
,React 開發者有一些建議給我們:
-
您可以依賴
**useMemo()**
作爲性能優化,而不是語義保證 -
函數內部引用的每個值也應該出現在依賴項數組中
對於我們的下一個示例,我們將對 <ParentComponent />
進行一些更改。下面的代碼僅顯示對我們之前創建的 <ParentComponent />
的新更改。
// components/parent-component.js
.
.
import { useState, useEffect, useRef, useMemo } from "react";
import UseMemoCounts from "./use-memo-counts";
export default function ParentComponent() {
.
.
const [times, setTimes] = useState(0);
const useMemoRef = useRef(0);
const incrementUseMemoRef = () => useMemoRef.current++;
// uncomment the next line to test that <UseMemoCounts /> will re-render every t ime the parent re-renders.
// const memoizedValue = useMemoRef.current++;
// the next line ensures that <UseMemoCounts /> only renders when the times value changes
const memoizedValue = useMemo(() => incrementUseMemoRef(), [times]);
.
.
return (
<div class>
.
.
<div class>
<button
class
onClick={() => setTimes(times+1)}
>
Force render
</button>
<UseMemoCounts memoizedValue={memoizedValue} />
</div>
</div>
);
}
首先,我們引入了非常重要的 useMemo()
Hook。我們還引入了 useRef()
Hook 來幫助我們跟蹤在我們的組件中發生了多少次重新渲染。接下來,我們聲明一個 times
狀態,稍後我們將更新該狀態來觸發 / 強制重新渲染。
之後,我們聲明一個 memoizedValue
變量,用於存儲 useMemo()
Hook 的返回值。useMemo()
Hook 調用我們的 incrementUseMemoRef
函數,它會在每次依賴項發生變化時將我們的 useMemoRef.current
值加一,即 times
值發生變化。
然後我們創建一個按鈕來點擊更新times
的值。單擊此按鈕將觸發我們的 useMemo()
Hook,更新 memoizedValue
的值,並重新渲染我們的 <UseMemoCounts />
組件。
在這個例子中,我們還將 <Counts />
組件重命名爲 <UseMemoCounts />
,它現在需要一個 memoizedValue
屬性。
這是它的樣子:
// components/use-memo-counts.js
function UseMemoCounts({memoizedValue}) {
return (
<div class>
<p class>
I'll only re-render when you click <span class>Force render.</span>
</p>
<p class>{memoizedValue} time(s)</span> </p>
</div>
);
}
export default UseMemoCounts;
現在,當我們單擊任何奶酪按鈕時,我們的 memoizedValue
不會更新。但是當我們單擊 Force render 按鈕時,我們看到 memoizedValue
更新並且 <UseMemoCounts />
組件重新渲染。
如果註釋掉我們當前的 memoizedValue 行,並取消註釋掉它上面的行:
const memoizedValue = useMemoRef.current++;
將看到 <UseMemoCounts />
組件在每次 <ParentComponent />
渲染時重新渲染。
總結:React.memo() 和 useMemo() 的主要區別
從上面的例子中,我們可以看到 React.memo()
和 useMemo()
之間的主要區別:
-
**React.memo()**
是一個高階組件,我們可以使用它來包裝我們不想重新渲染的組件,除非其中的 props 發生變化。 -
**useMemo()**
是一個 React Hook,我們可以使用它在組件中包裝函數。我們可以使用它來確保該函數中的值僅在其依賴項之一發生變化時才重新計算。
雖然 memoization 似乎是一個可以隨處使用的巧妙小技巧,但只有在絕對需要這些性能提升時才應該使用它。Memoization 會佔用運行它的機器上的內存空間,因此可能會導致意想不到的效果。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/zxT2GfujdbQfvrCtRxkbiQ