useMemo:根據(jù)名字翻譯為"備忘錄"楣黍,他在react的hooks中發(fā)揮什么樣的作用呢?
先根據(jù)查找到的資料棱烂,發(fā)現(xiàn)它的用法同useEffect很像租漂,也是接受第二個(gè)參數(shù)作為判斷是否執(zhí)行useMemo的依賴項(xiàng)。
其實(shí)它是解決這樣的一個(gè)場景中的問題颊糜,先看代碼:
import { useState, useMemo} from "react";
import * as ReactDOM from "react-dom";
// 產(chǎn)品名稱列表
const nameList = ['apple', 'peer', 'banana', 'lemon']
function App() {
// 產(chǎn)品名稱哩治、價(jià)格
const [price, setPrice] = useState(0)
const [name, setName] = useState('apple')
// 假設(shè)有一個(gè)業(yè)務(wù)函數(shù) 獲取產(chǎn)品的名字
function getProductName() {
console.log('getProductName觸發(fā)')
return name
}
return (
<>
<p>{price}</p>
<p>{getProductName()}</p>
<button onClick={() => setPrice(price+1)}>價(jià)錢+1</button>
<button onClick={() => setName(nameList[Math.random() * nameList.length << 0])}>修改名字</button>
</>
)
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
在這個(gè)示例中,getProductName算作是一個(gè)子組件衬鱼,從內(nèi)部可以看到它只是使用到了name业筏,因此可以認(rèn)為它只需要在name發(fā)生變更的時(shí)候執(zhí)行,其他的狀態(tài)和它無關(guān)鸟赫。特別的蒜胖,如果getProductName中是一個(gè)開銷非常大的計(jì)算消别,那么其他狀態(tài)的變更不應(yīng)該觸發(fā)它,否則就是一種浪費(fèi)翠勉。
那么這個(gè)問題該如何解決呢妖啥?似乎好像一下子想到了useEffect,因?yàn)楦鶕?jù)開頭所說可知useMemo與它的使用形式很相似对碌。但是突然又想到荆虱,useEffect的使用語法似乎并不適合這里的getProductName函數(shù),因?yàn)槲覀冎佬嗝牵瑄seEffect內(nèi)部返回的函數(shù)是在會(huì)在下一次useEffect執(zhí)行前執(zhí)行怀读,因此如果在useEffect中將getProductName返回,尚不清楚返回的還是不是getProductName函數(shù)本身骑脱,帶著這個(gè)問題菜枷,我們測試看看將useEffect賦給一個(gè)變量得到的是什么東西:
const test = useEffect(()=>{
function getProductName() {
console.log('getProductName觸發(fā)')
return name
}
return getProductName
}, [name])
console.log(test)
可以看到,將useEffect賦給一個(gè)變量且內(nèi)部返回一個(gè)函數(shù)叁丧,得不到任何想要的東西(test是undefined)啤誊。因此,使用useEffect的想法失敗拥娄。
這個(gè)時(shí)候讓咱們來嘗試useMemo吧蚊锹!
import { useState, useMemo, useEffect} from "react";
import * as ReactDOM from "react-dom";
// 產(chǎn)品名稱列表
const nameList = ['apple', 'peer', 'banana', 'lemon']
function App() {
// 產(chǎn)品名稱、價(jià)格
const [price, setPrice] = useState(0)
const [name, setName] = useState('apple')
// 假設(shè)有一個(gè)業(yè)務(wù)函數(shù) 獲取產(chǎn)品的名字
const memo_getProductName = useMemo(()=>{
console.log('getProductName觸發(fā)')
return name
}, [name])
console.log(memo_getProductName)
return (
<>
<p>{price}</p>
<p>{memo_getProductName}</p>
<button onClick={() => setPrice(price+1)}>價(jià)錢+1</button>
<button onClick={() => setName(nameList[Math.random() * nameList.length << 0])}>修改名字</button>
</>
)
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
可以看到useMemo生效了稚瘾,當(dāng)我點(diǎn)擊價(jià)格按鈕牡昆,只是價(jià)格變動(dòng),memo_getProductName內(nèi)部并沒有執(zhí)行摊欠。
同時(shí)發(fā)現(xiàn)初始進(jìn)入頁面的時(shí)候useMemo也是會(huì)執(zhí)行一次的丢烘,這和useEffect保持了一致,也就是相當(dāng)于在componentDidMount的生命周期會(huì)執(zhí)行些椒。
由此可得出這個(gè)結(jié)論:useMemo 返回一個(gè) memoized 值播瞳,在依賴參數(shù)不變的的情況返回的是上次第一次計(jì)算的值,當(dāng)依賴參數(shù)發(fā)生變化時(shí)useMemo就會(huì)自動(dòng)重新計(jì)算返回一個(gè)新的 memoized值免糕。
用自己的理解就是:useMemo基本上同useEffect起到一樣的作用狐史,只根據(jù)依賴項(xiàng)數(shù)組中的狀態(tài)是否發(fā)生變更來判斷自己是否需要執(zhí)行,只是useMemo的執(zhí)行可返回一個(gè)具體的結(jié)果供其他地方使用说墨。
當(dāng)然,useMemo并不一定需要在內(nèi)部返回一個(gè)什么苍柏,也可以是純粹的在useMemo中寫一些邏輯尼斧。以下是antd-design中upload組件有一段使用到了useMemo的代碼。
// Control mode will auto fill file uid if not provided
React.useMemo(() => {
const timestamp = Date.now();
(fileList || []).forEach((file, index) => {
if (!file.uid && !Object.isFrozen(file)) {
file.uid = `__AUTO__${timestamp}_${index}__`;
}
});
}, [fileList]);
只要fileList發(fā)生變更试吁,useMemo內(nèi)部就會(huì)執(zhí)行棺棵,useMemo并不需要將自己賦給誰去調(diào)用楼咳,這個(gè)時(shí)候和useEffect是完全一樣的作用。
與useEffect烛恤?
如上所述useMemo與useEffect的區(qū)別好像并不大母怜,那我是否可以隨意混用?帶著這個(gè)問題缚柏,且看官網(wǎng)的一段描述:
記住苹熏,傳入 useMemo 的函數(shù)會(huì)在渲染期間執(zhí)行。請不要在這個(gè)函數(shù)內(nèi)部執(zhí)行與渲染無關(guān)的操作币喧,諸如副作用這類的操作屬于 useEffect 的適用范疇轨域,而不是 useMemo。
你可以把 useMemo 作為性能優(yōu)化的手段杀餐,但不要把它當(dāng)成語義上的保證干发。將來,React 可能會(huì)選擇“遺忘”以前的一些 memoized 值史翘,并在下次渲染時(shí)重新計(jì)算它們枉长,比如為離屏組件釋放內(nèi)存。先編寫在沒有 useMemo 的情況下也可以執(zhí)行的代碼 —— 之后再在你的代碼中添加 useMemo琼讽,以達(dá)到優(yōu)化性能的目的必峰。
這說的什么意思?意思是大多數(shù)時(shí)候其實(shí)并不需要使用到useMemo跨琳,除非真的需要做性能優(yōu)化才考慮到它自点。
我的理解是這個(gè)api的調(diào)用或多或少也會(huì)占用一定的時(shí)間復(fù)雜度、空間復(fù)雜度等脉让,如果像我上述的例子只是很小的一個(gè)組件桂敛,完全沒有必要去使用它做所謂的優(yōu)化。包括useCallback溅潜、memo等术唬,應(yīng)避免無腦使用這些高開銷api。
那么說的“會(huì)在渲染期間執(zhí)行滚澜。請不要在這個(gè)函數(shù)內(nèi)部執(zhí)行與渲染無關(guān)的操作”該如何理解粗仓?來寫個(gè)例子看看。
import { useState, useMemo, useEffect } from "react";
import * as ReactDOM from "react-dom";
// 產(chǎn)品名稱列表
const nameList = ["apple", "peer", "banana", "lemon"];
function App() {
// 產(chǎn)品名稱设捐、價(jià)格
const [price, setPrice] = useState(0);
const [name, setName] = useState("apple");
// 假設(shè)有一個(gè)業(yè)務(wù)函數(shù) 獲取產(chǎn)品的名字
useMemo(() => {
setName(nameList[(Math.random() * nameList.length) << 0])
});
return (
<>
<p>{price}</p>
<p>{name}</p>
<button onClick={() => setPrice(price + 1)}>價(jià)錢+1</button>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
我在useMemo中使用嘗試使用setState改變一個(gè)狀態(tài)的值借浊,但編輯器自動(dòng)提示陷入死循環(huán),也就是說萝招,useMemo會(huì)在組件渲染的時(shí)刻執(zhí)行蚂斤,因?yàn)槌跏歼M(jìn)入頁面useMemo會(huì)執(zhí)行一次,所以name就會(huì)被變更槐沼,隨著name的變更曙蒸,頁面重新渲染捌治,useMemo在這次渲染中又執(zhí)行,name又被變更...這樣就陷入了死循環(huán)纽窟。
但是當(dāng)我在依賴數(shù)組中加入price的時(shí)候就不會(huì)陷入死循環(huán):
// 假設(shè)有一個(gè)業(yè)務(wù)函數(shù) 獲取產(chǎn)品的名字
useMemo(() => {
setName(nameList[(Math.random() * nameList.length) << 0])
}, [price]);
原因是肖油,在這里的useMemo中只是觸發(fā)name的變更,加上了price的依賴后排除了name引起的useMemo執(zhí)行臂港。但這么寫其實(shí)并沒有什么意義森枪,不如直接使用useEffect更直接(當(dāng)然,useEffect如果在不設(shè)依賴項(xiàng)的情況下也不應(yīng)該setState,否則也會(huì)陷入死循環(huán))。
因此還是官網(wǎng)推薦的:除非非有必要使用useMemo來做性能優(yōu)化才去選擇它模暗。
我自己感覺useMemo有點(diǎn)像vue中的computed聂示。
完。