React中memo useMemo useCallback的用法和區(qū)別

在對 React 項目做性能優(yōu)化的時候欠母,memeo爬早、useMemo氯夷、useCallback 三個API總是形影不離捍掺。

一撼短、memo

1.memo作用

在 React 的渲染流程中,一般來說挺勿,父組件的某個狀態(tài)發(fā)生改變阔加,那么父組件會重新渲染,父組件所使用的所有子組件满钟,都會強制渲染胜榔。而在某些場景中,子組件并沒有使用父組件傳入的沒有發(fā)生更改的狀態(tài)時湃番,子組件重新渲染是沒有必要的夭织。因此有了 React.memo

2.memo 的使用

memo 是個高階組件, 結(jié)合了 PurComponent 和 shouldComponentUpdate 功能吠撮,會對傳入的 props 進行淺比較尊惰,來決定是否更新被包裹的組件

memo 接受兩個參數(shù):

  • WrapComponent:你要優(yōu)化的組件
  • (prev, next) => boolean:通過對比 prev(舊 props),next(新 props)是否一致泥兰,返回 true(不更新)弄屡、false(更新)

注意:memo 只針對 props 來決定是否渲染,且是淺比較
現(xiàn)在我們來看一個的例子:

import { useState } from 'react';
const Child = () => (
    <div>{console.log('子組件渲染了')}</div>
);
function Parent() {
    const [status, setStatus] = useState(true);
    return (
        <div>
            <Child />
            <button
                onClick={() =>
                    setStatus(!status)
                }>
                {status ? 'on' : 'off'}
            </button>
        </div>
    );
}
export default Parent;

運行結(jié)果如下:


image.png

在上面的例子中鞋诗,父組件中的狀態(tài) status和 Child 組件沒有關系膀捷,當我點擊按鈕時,status 發(fā)生改變削彬,此時父組件重新渲染全庸,按鈕文案變?yōu)閛ff,控制臺卻打印出 "子組件又渲染" 的信息融痛,說明子組件也跟著重新渲染了壶笼。而這肯定是不合理的,我們不希望子組件做無關的刷新雁刷,此時我們可以給子組件加上memo

import { useState, memo } from 'react';
const Child = memo(() => (
    <div>{console.log('子組件渲染了')}</div>
));
function Parent() {
    const [status, setStatus] = useState(true);
    return (
        <div>
            <Child />
            <button
                onClick={() =>
                    setStatus(!status)
                }>
                {status ? 'on' : 'off'}
            </button>
        </div>
    );
}
export default Parent;
image.png

此時我們點擊按鈕覆劈,子組件不會被重新渲染

import { useState, memo } from 'react';
import PropTypes from 'prop-types';
const Child = memo((props) => (
    <div>
        {props.number}
        {console.log('子組件渲染了')}
    </div>
));
Child.propTypes = {
    number: PropTypes.number,
};
function Parent() {
    const [number, setNumber] = useState(1);
    return (
        <div>
            <Child number={number} />
            <button onClick={() => setNumber(2)}>
                點擊
            </button>
        </div>
    );
}
export default Parent;
image.png

在這個例子中,當我們點擊按鈕沛励,傳入子組件的number從1變?yōu)榱?责语,子組件的props發(fā)生了改變,重新渲染

總而言之侯勉,如果組件被 memo 包裹鹦筹,那么組件的 props 不發(fā)生改變時,組件不會重新渲染址貌。這樣铐拐,我們合理的使用 memo 就可以為我們的項目帶來很大的性能優(yōu)化

3.memo 的注意事項

memo 對于新舊 props 的比較默認是淺比較,當我們子組件接收的是一個引用類型的 props 的時候练对,可以自定義比較來決定是否需要使用緩存還是重新渲染

看下面的例子

import { useState, memo } from 'react';
import PropTypes from 'prop-types';
const Child = memo((props) => (
    <div>
        {`我叫${props.obj.name}`}
        {console.log('子組件渲染了')}
    </div>
));
Child.propTypes = {
    obj: PropTypes.shape({
        name: PropTypes.string,
        age: PropTypes.number,
    }),
};
function Parent() {
    const [obj, setObj] = useState({
        name: 'xxx',
        age: 18,
    });
    return (
        <div>
            <Child obj={obj} />
            <button
                onClick={() => {
                    setObj({
                        name: 'xxx',
                        age: 19,
                    });
                }}>
                點擊
            </button>
        </div>
    );
}
export default Parent;
image.png

我們點擊按鈕修改了age遍蟋,子組件的props發(fā)生了變化,重新渲染螟凭。但是子組件中并沒有用到age虚青,我們不需要它重新渲染,這個時候我們可以使用memo的第二個參數(shù)來自定義校驗規(guī)則

import { useState, memo } from 'react';
import PropTypes from 'prop-types';
const Child = memo(
    (props) => (
        <div>
            {`我叫${props.obj.name}`}
            {console.log('子組件渲染了')}
        </div>
    ),
    // 新舊name相同就不重新渲染
    (prev, next) => {
        return prev.obj.name === next.obj.name;
    },
);
Child.propTypes = {
    obj: PropTypes.shape({
        name: PropTypes.string,
        age: PropTypes.number,
    }),
};
function Parent() {
    const [obj, setObj] = useState({
        name: 'xxx',
        age: 18,
    });
    return (
        <div>
            <Child obj={obj} />
            <button
                onClick={() => {
                    setObj({
                        name: 'xxx',
                        age: 19,
                    });
                }}>
                點擊
            </button>
        </div>
    );
}
export default Parent;
image.png

這個時候我們點擊按鈕修改age螺男,子組件就不會重新渲染了棒厘。注意:默認情況下(沒有自定義校驗)即使引用對象的屬性值沒發(fā)生變化纵穿,但是地址改變了,也會引起子組件重新渲染奢人,例如上述例子中使用setObj({...obj})

因為緩存本身也是需要開銷的谓媒。如果每一個組件都用 memo 去包裹一下,那么對瀏覽器的開銷就會很大何乎,本末倒置了句惯。

所以我們應該選擇性的用 memo 包裹組件,而不是濫用

二支救、useMemo

1.useMemo 的作用

useMemo 它可以緩存一個結(jié)果抢野,當這個緩存結(jié)果不變時,可以借此來進行性能優(yōu)化各墨。
看下面的例子

import { useState } from 'react';
const Parent = () => {
    const [number, setNumber] = useState(0);
    function addNumber() {
        setNumber(number + 1);
    }
    const result = () => {
        console.log('計算result');
        for (let i = 0; i < 10000; i++) {
            i.toString();
        }
        return 1000;
    };
    return (
        <div>
            <div>result: {result()}</div>
            <div>number: {number}</div>
            <button onClick={() => addNumber()}>
                click
            </button>
        </div>
    );
};
export default Parent;
image.png

當我們點擊按鈕指孤,number每次點擊都會加1,result方法也會隨著重新計算一遍欲主,每次都要進行大量的for循環(huán)邓厕,很耗費性能,這種情況下我們可以使用useMemo來進行優(yōu)化

2.useMemo 的使用

useMemo 接受兩個參數(shù):

  • callback:計算結(jié)果的執(zhí)行函數(shù)
  • deps:相關依賴項數(shù)組

最終 useMemo 在執(zhí)行了 callback 后扁瓢,返回一個結(jié)果详恼,這個結(jié)果就會被緩存起來。當 deps 依賴發(fā)生改變的時候引几,會重新執(zhí)行 callback 計算并返回最新的結(jié)果昧互,否則就使用緩存的結(jié)果
我們來把上面的例子用 useMemo 改造一下

import { useState, useMemo } from 'react';
const Parent = () => {
    const [number, setNumber] = useState(0);
    function addNumber() {
        setNumber(number + 1);
    }
    const result = useMemo(() => {
        console.log('計算result');
        for (let i = 0; i < 10000; i++) {
            i.toString();
        }
        return 1000;
    }, []);
    return (
        <div>
            <div>result: {result}</div>
            <div>number: {number}</div>
            <button onClick={() => addNumber()}>
                click
            </button>
        </div>
    );
};
export default Parent;
image.png

現(xiàn)在不論我們怎么去改變number的值,result都不會重新運行伟桅,這樣就達到了性能優(yōu)化的目的
useMemo 并不是用的越多越好敞掘,緩存本身也需要開銷,一些簡單的計算方法就沒必要使用useMemo

3.useMemo配合memo使用

import { useState, memo } from 'react';
const Child = memo(() => {
    console.log('子組件渲染');
    return <div>子組件</div>;
});
const Parent = () => {
    const [number, setNumber] = useState(0);
    function addNumber() {
        setNumber(number + 1);
    }
    const result = () => {
        console.log('計算result');
        return 1000;
    };
    return (
        <div>
            <div>result: {result}</div>
            <div>number: {number}</div>
            <button onClick={() => addNumber()}>
                click
            </button>
            <Child result={result} />
        </div>
    );
};
export default Parent;

image.png

上面的例子中楣铁,result函數(shù)作為props傳給了子組件玖雁,即使子組件被memo包裹著,但還是重新渲染了盖腕,這是因為赫冬,父組件重新渲染時,又創(chuàng)建了一個函數(shù)(或者說又開辟了一個內(nèi)存地址)賦值給 result溃列,而 memo 只做淺比較劲厌,發(fā)現(xiàn)地址改變了,所以子組件重新渲染听隐,這個時候就需要使用 useMemo 來進行優(yōu)化

import { useState, memo, useMemo } from 'react';
const Child = memo(() => {
    console.log('子組件渲染');
    return <div>子組件</div>;
});
const Parent = () => {
    const [number, setNumber] = useState(0);
    function addNumber() {
        setNumber(number + 1);
    }
    const result = useMemo(() => {
        console.log('計算result');
        return 1000;
    }, []);
    return (
        <div>
            <div>result: {result}</div>
            <div>number: {number}</div>
            <button onClick={() => addNumber()}>
                click
            </button>
            <Child result={result} />
        </div>
    );
};
export default Parent;
image.png

此時补鼻,再次點擊按鈕修改 number 后,子組件不會重新更新,達到了性能優(yōu)化的目的

三风范、useCallback

1.useCallback 的作用

useCallback 類似于 useMemo咨跌,只不過 useCallback 用于緩存函數(shù)罷了,同樣可以防止無關的刷新硼婿,對組件做出性能優(yōu)化

2.useCallback 的使用

useCallback 同樣接受兩個參數(shù):

  • callback:傳入子組件的函數(shù)
  • deps:相關依賴項數(shù)組

最終 useCallback 會把傳入的 callback 緩存起來虑润。當 deps 依賴發(fā)生改變的時候,會重新緩存最新的 callback 加酵,否則就使用緩存的結(jié)果

單獨使用 useCallback 起不到優(yōu)化的作用,反而會增加性能消耗哭当,需要和 memo 一起使用

我們來把上面的例子用 useCallback 改造一下

import {
    useState,
    memo,
    useCallback,
} from 'react';
const Child = memo(() => {
    console.log('子組件渲染');
    return <div>子組件</div>;
});
const Parent = () => {
    const [number, setNumber] = useState(0);
    function addNumber() {
        setNumber(number + 1);
    }
    const result = useCallback(() => {
        console.log('計算result');
    }, []);
    return (
        <div>
            <div>result: {result}</div>
            <div>number: {number}</div>
            <button onClick={() => addNumber()}>
                click
            </button>
            <Child result={result} />
        </div>
    );
};
export default Parent;

點擊按鈕修改 number 后猪腕,子組件不會重新更新,達到了性能優(yōu)化的目的

總結(jié)

memo:

  • 父組件重新渲染钦勘,沒有被 memo 包裹的子組件也會重新渲染
  • 被 memo 包裹的組件只有在 props 改變后陋葡,才會重新渲染
  • memo 只會對新舊 props 做淺比較,所以對于引用類型的數(shù)據(jù)如果發(fā)生了更改彻采,需要返回一個新的地址
  • memo 并不是用的越多越好腐缤,因為緩存本身也是需要開銷的。如果每一個組件都用 memo 去包裹一下肛响,那么對瀏覽器的開銷就會很大岭粤,本末倒置了
  • 項目中可以針對刷新頻率高的組件,根據(jù)實際情況特笋,使用 memo 進行優(yōu)化

useMemo:

  • useMemo 是對計算的結(jié)果進行緩存剃浇,當緩存結(jié)果不變時,會使用緩存結(jié)果
  • useMemo 并不是用的越多越好猎物,對于耗時長虎囚、性能開銷大的地方,可以使用 useMemo 來優(yōu)化蔫磨,但大多數(shù)情況下淘讥,計算結(jié)果的開銷還沒有使用 useMemo 的開銷大,應視情況而定
  • 當父組件傳了一個引用類型的結(jié)果 result 給子組件堤如,且子組件用 memo 包裹時蒲列,需要使用 useMemo 對 result 進行緩存,因為 memo 只對 props 做淺比較煤惩,當父組件重新渲染時嫉嘀,會重新在內(nèi)存中開辟一個地址賦值給 result,此時地址發(fā)生改變魄揉,子組件會重新渲染

useCallback:

  • useCallback 與 useMemo 類似剪侮,只不過是對函數(shù)進行緩存
  • useCallback 可以單獨使用,但是單獨使用的使用對性能優(yōu)化并沒有實質(zhì)的提升,且父組件此時重新渲染瓣俯,子組件同樣會渲染
  • useCallback 需要配合 memo 一起使用杰标,這樣當父組件重新渲染時,緩存的函數(shù)的地址不會發(fā)生改變彩匕,memo 淺比較會認為 props 沒有改變腔剂,因此子組件不會重新渲染
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市驼仪,隨后出現(xiàn)的幾起案子掸犬,更是在濱河造成了極大的恐慌,老刑警劉巖绪爸,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湾碎,死亡現(xiàn)場離奇詭異,居然都是意外死亡奠货,警方通過查閱死者的電腦和手機介褥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來递惋,“玉大人柔滔,你說我怎么就攤上這事∑妓洌” “怎么了睛廊?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贩挣。 經(jīng)常有香客問我喉前,道長,這世上最難降的妖魔是什么王财? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任卵迂,我火速辦了婚禮,結(jié)果婚禮上绒净,老公的妹妹穿的比我還像新娘见咒。我一直安慰自己,他們只是感情好挂疆,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布改览。 她就那樣靜靜地躺著,像睡著了一般缤言。 火紅的嫁衣襯著肌膚如雪辆它。 梳的紋絲不亂的頭發(fā)上睬罗,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音,去河邊找鬼俏脊。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虏辫,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锈拨!你這毒婦竟也來了砌庄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤奕枢,失蹤者是張志新(化名)和其女友劉穎娄昆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缝彬,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡稿黄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了跌造。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡族购,死狀恐怖壳贪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寝杖,我是刑警寧澤违施,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站瑟幕,受9級特大地震影響磕蒲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜只盹,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一辣往、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧殖卑,春花似錦站削、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至菩鲜,卻和暖如春园细,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背接校。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工猛频, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓伦乔,卻偏偏與公主長得像厉亏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子烈和,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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