Demo1 Codesandbox
Demo2 Codesandbox
我們知道影響 React 重新渲染的兩大關鍵 props / state ,當virtual-DOM發現props或state改變時,就會渲染使用這些數據對應的畫面UI。因此在優化效能的路上,就是需要減少一些不必要但卻很昂貴的渲染。
昨天我們談到了useMemo,可以在某個對應的state沒變時,不重新渲染某個數值及其畫面。那props也有個對應的工具存在,也就是說當props沒改變時,我們就不去重新渲染畫面。這個東西便是React.memo。
React.memo是一個HOC (Higher Order Component),什麼是HOC呢?
把一個元件丟進HOC他會幫你做些事情,再還你加工過的元件;就好比高階函數 (Higher Order Function) !
React 官網
React.memo 這個HOC就是幫你比較元件當中的 props 有沒有改變。
用法如下
function FancyButton(props) {
    ... 元件
}
function areEqual(prevProps, nextProps) {
    ... 根據props回傳 true / false 的函式
}
export default React.memo(FancyButton, areEqual);
/*
或是const MyComponent = React.memo(function MyComponent(props) {
    ...
});
*/
高階函數滿足以下至少一個條件
1.是指將某個函數作為參數(arguments)給另一個函數
2.某個函數返回(return)一個函數。
【wiki】higher-order function
實際舉個常見的例子
// 1 (arguments) 匿名函式 傳入 setTimeout
    setTimeout(() => alert('Hello'), 3000)
    
// 2 (arguments) 函式 isToyota 傳入 Array.prototype.filter
    const cars = [
      { make: 'Ford', model: 'Focus' },
      { make: 'Ford', model: 'Kuga' },
      { make: 'Toyota', model: 'Wish' },
      { make: 'Toyota', model: 'Yaris' },
    ]
    const isToyota = car => car.make === 'Toyota'
    console.log(cars.filter(isToyota))
    //  [
    //    { make: 'Toyota', model: 'Wish' },
    //    { make: 'Toyota', model: 'Yaris' },
    //  ]
    
// 3 (return) 回傳出一個函式存入 addTen
    function add(x) {
      return function (y) {
        return x + y
      }
    }
    let addTen = add(10)
    addTen(7) // 17
React.memo(CustomComponent, areEqual);,當中有兩個參數
1.需要做效能優化、比較 props 的元件本人。
2.想要比較的 props 有哪些。 如果你沒輸入,這個參數預設為比較整個 props 內所有數值。
App 當中有 2 個 state ,分別是 count1, count2,各自也有顆遞增的按鈕。
          const [count1, setCount1] = useState(0);
          const [count2, setCount2] = useState(0);
          ------------------------------
          <button onClick={() => setCount1(count1 + 1)}> count1 is {count1} </button>
          <button onClick={() => setCount2(count2 + 1)}> count2 is {count2} </button>
把一個 Child 元件放入App,並且提供一個 count1 作為其 props。
          const Child = ({ count }) => {
              console.log("====== Child Render ======");
              return <div>count1 is {count}</div>;
          };
          <button onClick={() => setCount1(count1 + 1)}> count1 is {count1} </button>
          <Child count={count1} />
          <button onClick={() => setCount2(count2 + 1)}> count2 is {count2} </button>
此時我們發現,不管點 count1 或 count2 的 按鈕, 因為App本身有state改變了,底下的Child都會被重新渲染。
但對於 <Child count={count1} /> 而言,它關注的props只有count1而已,假如這Child的渲染很複雜,我們不希望它一直被重新渲染,我們可以使用React.memo。
首先我們透過建立另外一個元件作為比較。
            const Child = ({ count }) => {
              console.log("====== Child Render ======");
              return <div>count1 is {count}</div>;
            };
            const ChildWithMemo = React.memo(({ count }) => {
              console.log("====== Child with Memo Render ======");
              return <div>count is {count}</div>;
            });
          <button onClick={() => setCount1(count1 + 1)}> count1 is {count1} </button>
          <Child count={count1} />
          <ChildWithMemo count={count1} />
          <button onClick={() => setCount2(count2 + 1)}> count2 is {count2} </button>
Demo1 Codesandbox
此時我們便可以發現,當 count2 改變時,包上memo的 <ChildWithMemo count={count1} /> 元件,不會再被重新渲染了。
App 當中有 1 個 state 叫 perosn, person是個物件,裡面包含了 姓名 / 年齡 / 薪水,這三個屬性。
我們有兩顆按鈕,一個叫加薪、一個叫做變老 QwQ,分別會對 person 更新 年齡 跟 薪水
        const [person, setPerson] = useState({
            name: "Ken",
            age: 20,
            salary: 30000
        });
        const raiseSalary = () =>
            setPerson({ ...person, salary: parseInt(person.salary * 1.25) });
        const raiseAge = () => setPerson({ ...person, age: person.age + 1 });
        <div className="App">
            <button onClick={raiseSalary}>加薪</button>
            <button onClick={raiseAge}>變老</button>
            <div> 姓名 {person.name}</div>
            <div> 年齡 {person.age}</div>
            <DemoSalary person={person} />
        </div>
        const DemoSalary = ({ person }) => {
          console.log("====== DemoSalary render ======");
          return <div> 薪水 {person.salary}</div>;
        }
今天我們希望這有個<DemoSalary person={person} />,只有當薪水改變時,我們才會重新渲染,這時候我們就可以使用到React.memo(元件,比較函式)的第二個參數啦!當上個props跟下個props的薪水是一樣時,我們就回傳true,否則回傳false。
        const isSalaryEqual = (prevProps, nextProps) =>
          prevProps.person.salary === nextProps.person.salary;
接著著再把這個function,放進React.memo的第二個參數,便大功告成啦!
        const DemoSalary = React.memo(({ person }) => {
          console.log("====== DemoSalary render ======");
          return <div> 薪水 {person.salary}</div>;
        }, isSalaryEqual);
Demo2 codesandbox link
危險~! 這邊必須注意的是,如果你僅單純比較某些 props 特定的部分,你必須確保畫面中沒有用到 props 其他部分。否則他們的畫面將不會被更新到。 假如說我 DemoSalary 當中顯示了名字,但你更改姓名時,畫面不會更新你的名字。
pure functional 是指同一個function的input不管執行多少次,output永遠都會一樣。元件而言就是,相同的props進去幾次,畫面永遠都一樣。
因為我們優化了效能,不渲染多餘的畫面時,不能遺漏渲染某些畫面的可能性(可能元件有某些side effect),因為畫面不會重新渲染,所以我們必須保證每次同樣的props都會產生相同的畫面。
React 性能優化那件大事,使用 memo、useCallback、useMemo
React.memo 与 useMemo ai哟 知乎
//Fooish 程式技術// React State
Javascript面面觀:核心篇《高階函數》
前端工程研究:理解函式編程核心概念與如何進行 JavaScript 函式編程
Use React memo Wisely
[譯] 詳解 React 渲染