React Hook - 如何使用useMemo和useCallback

Hook是在React 16.8之后增加的一項新功能,能夠幫助我們在不寫class的情況下使用state和其他React的相關特性。關于如何使用Hook官網有很多介紹,但理論是一碼事,實踐又是另一碼事。

如果寫過一段時間的React,我們知道通過使用 useMemouseCallback 能夠幫助我們的代碼提高性能,避免component在re-render時的無效計算。于是,慢慢的大家會發現前端代碼里到處都是 useMemouseCallback,不僅影響代碼的可讀性,而且也不利于debug。

本文會通過舉例等方式告訴大家,其實我們前端工程中幾乎90%的 useMemouseCallback 都是可以移除的,而且代碼不會有任何問題,且啟動加載速度會更快。

為什么需要 useMemo和 useCallback

為了在re-render之間能夠進行緩存。如果hook包裹了某個值或者方法,react會在初始渲染時對其進行緩存,并在多次渲染時對該保存值進行引用。如果沒有hook,非原生類型如數組、對象和方法會在每次重新渲染時重新創建。例如:

const a = { "test": 1 };
const b = { "test": 1'};

console.log(a === b); // will be false

const c = a; // "c" is just a reference to "a"

console.log(a === c); // will be true

另一個更貼近react代碼的例子:

const Component = () => {
  const a = { test: 1 };

  useEffect(() => {
    // "a" will be compared between re-renders
  }, [a]);

  // the rest of the code
};

auseEffect的一個依賴值,React每次重新渲染 Component 時都會與其之前的值進行對比。a是在 Component 中定義的一個對象,因此每次重新渲染都會重新創建。因此“渲染前”的a與“渲染后”的 a比較結果為不想等,因此 useEffect 也會在每次重新渲染時被觸發。
為了避免上述情況,我們可以對a使用 useMemo

const Component = () => {
  // preserving "a" reference between re-renders
  const a = useMemo(() => ({ test: 1 }), []);

  useEffect(() => {
    // this will be triggered only when "a" value actually changes
  }, [a]);

  // the rest of the code
};

現在只有當a的值真的發生變化時才會觸發useEffect
useCallback的使用與上述類似,只是它更常用于方法:

const Component = () => {
  // preserving onClick function between re-renders
  const fetch = useCallback(() => {
    console.log('fetch some data here');
  }, []);

  useEffect(() => {
    // this will be triggered only when "fetch" value actually changes
    fetch();
  }, [fetch]);

  // the rest of the code
};

這里要注意,useMemouseCallback只有在重新渲染期間才有用,在初始渲染時,他們的使用會導致react做更多的事情,所以程序也會更慢一些。如果你的代碼到處都是用了成百上千個hook,這種減速甚至是肉眼可感知的。

為什么Component會重新渲染

兩種場景:

  1. 當Component的state或者prop發生變化時,React會重新渲染該Component
  2. 當父Component被重新渲染時
    即當一個Component重新渲染它自己時,它也會重新渲染它所有的子Component,例如:
const App = () => {
  const [state, setState] = useState(1);

  return (
    <div className="App">
      <button onClick={() => setState(state + 1)}> click to re-render {state}</button>
      <br />
      <Page />
    </div>
  );
};

App Component有一些state也有一些自Component,例如 Page,當點擊頁面上的button,state會發生改變,因此會觸發 App的重新渲染,因此會觸發其所有子Component的重新渲染,包括 Page,即使他沒有props。
而如果在 Page 內部,還有一些其他子Component:

const Page = () => <Item />;

即使該子Component沒有state也沒有props,但也會由于App的重新渲染而被觸發重新渲染。由此得出結論,App由于其state變化而觸發的重新渲染,會觸發整個程序的重新渲染鏈。
如何中斷這條重新渲染鏈呢?對子Component進行緩存:

  1. 使用 useMemo hook
  2. 使用 React.memo 工具
    只有通過上面兩種方法,React才會在重新渲染之前停止并檢查其props值是否改變:
const Page = () => <Item />;
const PageMemoized = React.memo(Page);

const App = () => {
  const [state, setState] = useState(1);

  return (
    ... // same code as before
      <PageMemoized />
  );
};

有且只有在上述情況下,討論props是否被緩存才有意義。

例如:

const App = () => {
  const [state, setState] = useState(1);
  const onClick = () => {
    console.log('Do something on click');
  };
  return (
    // page will re-render regardless of whether onClick is memoized or not
    <Page onClick={onClick} />
  );
};

如果 Page沒有被緩存,當 App 重新渲染時,React發現 Page是其子Component,就會也重新渲染它。那么不論 onClick 是否使用useCallback都是沒有意義的;

對上述例子進一步優化,緩存 Page:

const PageMemoized = React.memo(Page);

const App = () => {
  const [state, setState] = useState(1);
  const onClick = () => {
    console.log('Do something on click');
  };
  return (
    // PageMemoized WILL re-render because onClick is not memoized
    <PageMemoized onClick={onClick} />
  );
};

如果 Page被緩存,當 App 重新渲染時,React發現 PageMemoized是其子Component且已使用 React.memo,因此中斷重新渲染鏈條,首先檢查 PageMemoized 的 props 是否發生變化。上述例子中,由于 onClick 沒有被緩存,所以props發生變化,PageMemoized 會被重新渲染;

對上述例子繼續優化,緩存 Page,使用 useCallback :

const PageMemoized = React.memo(Page);

const App = () => {
  const [state, setState] = useState(1);
  const onClick = useCallback(() => {
    console.log('Do something on click');
  }, []);

  return (
    // PageMemoized will NOT re-render because onClick is memoized
    <PageMemoized onClick={onClick} />
  );
};

那么,React在PageMemoized 上停止重新渲染鏈并檢查其props,發現 onClick沒有發生變化,因此PageMemoized 也不會被重新渲染;

對上述例子繼續變種,在PageMemoized 上增加另一個沒有緩存的值:

const PageMemoized = React.memo(Page);

const App = () => {
  const [state, setState] = useState(1);
  const onClick = useCallback(() => {
    console.log('Do something on click');
  }, []);

  return (
    // page WILL re-render because value is not memoized
    <PageMemoized onClick={onClick} value={[1, 2, 3]} />
  );
};

那么,React在PageMemoized 上停止重新渲染鏈并檢查其props,發現 onClick沒有發生變化,但是value發生變化,因此PageMemoized 會被重新渲染;

綜上所述,得出結論:只有Component本身和它的每一個props都被緩存時,hook的優化才有意義。否則都是對內存的浪費,且會降低代碼的可讀性。

參考文章

How to useMemo and useCallback: you can remove most of them

?著作權歸作者所有,轉載或內容合作請聯系作者
禁止轉載,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容

  • 因為篇幅原因,React hook的由來和影響這里不做介紹。本文主要介紹的是hook的基本API,還有從class...
    Yong_bcf4閱讀 1,432評論 0 2
  • React Hook是React函數式組件,它不僅僅有函數組件的特性,還帶有React框架的特性。所以,官網文檔多...
    娜姐聊前端閱讀 448評論 3 1
  • 要點: 可以在不編寫class的情況下使用 state 以及其他react特性。 Hook沒有破壞性改動, 完全是...
    koala949閱讀 866評論 0 0
  • useState useState 返回的第一個值將始終是更新后最新的 state,并且與 class 組件中的 ...
    三粒黑子球閱讀 463評論 0 0
  • 一、組件類 React的核心是組件, 在v16.8之前,組件的標準寫法是類(class)。 以下為一個簡單的組件類...
    郭_小青閱讀 718評論 1 5