iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Modern Web

React 走出新手村 系列 第 9

React 走出新手村-Memo處方簽

  • 分享至 

  • xImage
  •  

Memo解方

今天我們接續前面的問題,大家是否有找到解答了呢?
https://ithelp.ithome.com.tw/upload/images/20230906/20129020ck1xsZ4Fqf.jpg

如果沒有的話,我會在下面提供兩種做法:

  • 第一種,在 memo 裡面用 callback 告訴它從哪裡比較:

    import React, { memo, useCallback, useMemo, useState } from "react";
    
    // 子層接 props 參數,觸發渲染
    const Swatch = ({ params }) => {
      console.log(`Swatch 渲染 ${params.color}`);
      return (
        <div
          style={{
            margin: "1rem",
            width: 75,
            height: 75,
            borderRadius: "50%",
            backgroundColor: params.color
          }}
        ></div>
      );
    };
    
    // 第一種解決的方法,直接告訴他比較機制
    const MemoSwatch = memo(Swatch, (prevProps, nextProps) => {
      return prevProps.params.color === nextProps.params.color;
    });
    
    // 母層
    const MemoExample = () => {
      const [appRenderIdx, setAppRenderIdx] = useState(0);
      const [clr, setClr] = useState("blue");
      console.log(`Memo 渲染次數 ${appRenderIdx}`);
    
      return (
        <div>
          <h4>React Memo 範例</h4>
          <div className="f-b-c">
            <button onClick={() => setAppRenderIdx(appRenderIdx + 1)}>
              重新渲染
            </button>
            <button
              onClick={() => (clr === "blue" ? setClr("red") : setClr("blue"))}
            >
              換顏色
            </button>
          </div>
          <MemoSwatch params={{ color: clr }} />
        </div>
      );
    };
    
    export default MemoExample;
    
  • 第二種,使用 useMemo hook 去處理 params:

    import React, { memo, useCallback, useMemo, useState } from "react";
    
    // 子層接 props 參數,觸發渲染
    const Swatch = ({ params }) => {
      console.log(`Swatch 渲染 ${params.color}`);
      return (
        <div
          style={{
            margin: "1rem",
            width: 75,
            height: 75,
            borderRadius: "50%",
            backgroundColor: params.color
          }}
        ></div>
      );
    };
    
    const MemoSwatch = memo(Swatch);
    
    // 母層
    const MemoExample = () => {
      const [appRenderIdx, setAppRenderIdx] = useState(0);
      const [clr, setClr] = useState("blue");
      console.log(`Memo 渲染次數 ${appRenderIdx}`);
      // 第二種解決辦法,使用useMemo處理帶入的參數
        // 一樣是帶有dependency array所以當代入職參數變更時會告訴外層需要重新渲染
      const params = useMemo(() => ({ color: clr }), [clr]);
      return (
        <div>
          <h4>React Memo 範例</h4>
          <div className="f-b-c">
            <button onClick={() => setAppRenderIdx(appRenderIdx + 1)}>
              重新渲染
            </button>
            <button
              onClick={() => (clr === "blue" ? setClr("red") : setClr("blue"))}
            >
              換顏色
            </button>
          </div>
          <MemoSwatch params={params} />
        </div>
      );
    };
    
    export default MemoExample;
    

那麼你的畫面應該就又回到正常了:
https://ithelp.ithome.com.tw/upload/images/20230904/20129020nrDjzbUDXH.png

當按下重新渲染,子層不會被觸發了:
https://ithelp.ithome.com.tw/upload/images/20230904/20129020uYw07aORaB.png

按下換顏色才會觸發:
https://ithelp.ithome.com.tw/upload/images/20230904/20129020S6mxOfWdZw.png

帶 dependencies array 的 hooks

那麼既然都提到 useMemo 了,我們就順便連 useCallback 的使用也一併講一講,在先前 useEffect 的章節也講解了 dependencies array的作用。

useMemo

那麼在 useMemo 你可以單純理解為回吐資料型別為物件或陣列,也就是依照你 dependencies array 填入的資料判斷是否需要更新資料。

useCallback

useCallback 的部分也是一樣的概念,只是這次針對的對象為函數(function),下面我們舉個沒用 useCallback 的例子吧!

import React, { useState } from 'react';
// 母層
const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <ChildComponent onClick={increment} />
      <p>Count: {count}</p>
    </div>
  );
}
// 子層
const ChildComponent = ({ onClick }) => {
  console.log('我被渲染啦');
  return <button onClick={onClick}>Increment</button>;
}

export default ParentComponent;

假設我們有一個母組件和一個子組件,子組件接一個 onClick callback 作為 props,當母組件重新渲染時,每次都會重新創這個 increment 函數給子組件去接 onClick callback;

這會導致子組件在母組件重新渲染時不必要地重做一次,因為對子組件而言,永遠是收到了一個新的 refference。

那麼,我們使用 useCallback 來優化這個情況:

import React, { useState, useCallback } from 'react';
// 母層
const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  return (
    <div>
      <ChildComponent onClick={increment} />
      <p>Count: {count}</p>
    </div>
  );
}
// 子層不變
const ChildComponent = ({ onClick }) => {
  console.log('我被渲染啦');
  return <button onClick={onClick}>Increment</button>;
}

export default ParentComponent;

當母組件重新渲染時,由於我們使用了 useCallbackincrement 函數只會在 dependencies array 發生變化時才重新建立。在這個情況下,我們的 increment 函數的是 dependencies array 空陣列 [],這代表它不會在母組件的重新渲染中重新建立,來達到減少了子組件的不必要重新渲染。

總結

那麼今天的內容就到這裡,下一篇我們來認識 useMemouseCallback 的使用上都該注意哪些問題。

給全新手的大禮包

React基本Hook教學


上一篇
React 走出新手村-深入Memo
下一篇
React 走出新手村-useMemo & useCallback 小技巧
系列文
React 走出新手村 31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言