跳至主要内容

ReactHooks (二)

在某些情況下, 當父層重新render時, 即使子層的 props或者state 沒有任何變化也會跟著 reRender, 為了避免此情況發生,

根據不同情況可以使用 useMemo React.memo useCallback 來避免。

React.memo

定義

  • React.memo 寫在子組件
  • React.memo(Component, [areEqual(prevProps, nextProps)]);

範例

  • 畫面為一個 75x75 的正方形, 父層放兩個按鈕, 一個用來觸發畫面 Re-Render, 一個用來更換正方形顏色
    觸發子層 Re-Render
  • 若子層不用 React.memo 包起來, 每次按 Re-Render App 按鈕都會觸發子層 Re-Render, 即使 prop值並未更改
import { useState, memo } from "react";

function Swatch({ color }) {
console.log(`Swatch rendered ${color}`)
return <div style={{ margin: 2, width: 75, height: 75, backgroundColor: color }}></div>;
}

// 若沒有用memo包起來, 在每次的 reRender 都會觸發子層
const MemoedSwatch = memo(Swatch)

export default function App() {
const [appRenderIndex, setAppRenderIndex] = useState(0);
const [color, setColor] = useState("red")

console.log("app render", { appRenderIndex });

return (
<div className="App">
<div>
<button onClick={() => setAppRenderIndex(appRenderIndex + 1)}>
Re-Render App
</button>
<button onClick={() => setColor(color === "red" ? "blue" : "red")}>
Change Color
</button>
</div>

<MemoedSwatch color={color} />
</div>
);
}

useMemo

定義

  • 記憶的值不變就不會觸發重新渲染, 可用於阻擋子層組件重複render(物件的props)
  • 複雜的計算, 阻止重複大量計算

範例

接續上面範例, 這次把<MemoSwatch>改為傳入物件形式, ex. <MemoSwatch params={{color}}>,接著點 Re-Render App 按鈕可以看到子層的值沒有變化, 但又觸發Re-Render了

這是因為 React.memo 組件的值如果是屬於 primitive 型態會比較其 value, 如果是 object 型態則會比較其記憶體的位置, 而每次Re-Render後的記憶體位子都不相同, 所以就會造成 React.memo 失效.

  • 解一

用 React.memo 的 prev, next 來比較新舊值變化

// ...
const MemoSwatch = memo(Swatch, (prevProps, nextProps) => {
return prevProps.params.color === nextProps.params.color
});
// ...
  • 解二

要傳到子層的 params 用 useMemo 包起來比較

// ...
const params = useMemo(() => ({ color }), [color]);
// ...
<MemoSwatch params={params} />
  • 總覽
import { useState, memo, useMemo } from "react";

function Swatch({ params }) {
console.log(`Swatch rendered ${color}`)
return (
<div
style={{
margin: 2,
width: 75,
height: 75,
backgroundColor: params.color
}}
></div>
);
}

const MemoSwatch = memo(Swatch);

export default function App() {
const [appRenderIndex, setAppRenderIndex] = useState(0);
const [color, setColor] = useState("red");

console.log("app render", { appRenderIndex });

const params = useMemo(() => ({ color }), [color]);

return (
<div className="App">
<button onClick={() => setAppRenderIndex(appRenderIndex + 1)}>
Re-Render App
</button>
<button onClick={() => setColor(color === "red" ? "blue" : "red")}>
Change Color
</button>

<MemoSwatch param={param} />
</div>
);
}

useCallback

定義

  • 阻止畫面Re-Render造成同樣的function重複被Re-Render

範例

  • 接續上面範例, 這次在<MemoSwatch param={param} />再傳入一個 function,接著點 Re-Render App 按鈕可以看到子層又觸發Re-Render了
// ...
<MemoSwatch param={param} onClick={() => {}}/>
// ...
  • 這是因為function 同樣在每次 Re-Render 都會是新的 function, 所以 memo 就又會失效

而這情況就能使用 useCallback 把 function 先包起來, 如此一來就能又再一次解決 Re-Render 問題

// ...
const onClick = useCallback(() => {}, [])
// ...
<MemoSwatch params={params} onClick={onClick} />
  • 總覽
import { useState, memo, useMemo, useCallback } from "react";

function Swatch({ params }) {
console.log("colorBox", params.color);
return (
<div
style={{
margin: 2,
width: 75,
height: 75,
backgroundColor: params.color
}}
></div>
);
}

const MemoSwatch = memo(Swatch);

export default function App() {
const [appRenderIndex, setAppRenderIndex] = useState(0);
const [color, setColor] = useState("red");

console.log("app render", { appRenderIndex });

const onClick = useCallback(() => {}, [])
const params = useMemo(() => ({ color }), [color]);
return (
<div className="App">
<button onClick={() => setAppRenderIndex(appRenderIndex + 1)}>
Re-Render App
</button>
<button onClick={() => setColor(color === "red" ? "blue" : "red")}>
Change Color
</button>

<MemoSwatch params={params} onClick={onClick} />
</div>
);
}

小結

  • memo(淺層比較) 組建比對props, 並且props是淺層比較, 不適用深層
  • useMemo(深層比較) 陣列,物件
  • useCallback(function比較)