useMemo

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);
微信截圖_20211207172607.png

在這個(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)
image.png

可以看到,將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);
image.png

可以看到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);
image.png

我在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聂示。

完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌杂数,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘸洛,死亡現(xiàn)場離奇詭異揍移,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)反肋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門那伐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人石蔗,你說我怎么就攤上這事罕邀。” “怎么了养距?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵诉探,是天一觀的道長。 經(jīng)常有香客問我棍厌,道長肾胯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任耘纱,我火速辦了婚禮敬肚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘束析。我一直安慰自己帘皿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布畸陡。 她就那樣靜靜地躺著鹰溜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丁恭。 梳的紋絲不亂的頭發(fā)上曹动,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音牲览,去河邊找鬼墓陈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛第献,可吹牛的內(nèi)容都是我干的贡必。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼庸毫,長吁一口氣:“原來是場噩夢啊……” “哼仔拟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起飒赃,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤利花,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后载佳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炒事,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年蔫慧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挠乳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姑躲,死狀恐怖睡扬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肋联,我是刑警寧澤威蕉,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站橄仍,受9級(jí)特大地震影響韧涨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜侮繁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一虑粥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宪哩,春花似錦娩贷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茁瘦。三九已至,卻和暖如春储笑,著一層夾襖步出監(jiān)牢的瞬間甜熔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工突倍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腔稀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓羽历,卻偏偏與公主長得像焊虏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子秕磷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容