# [筆記] React 效能優化
# React 的 render 原理
react 具有 virtual DOM 機制,來處理 CSR 因為資料變更需要頻繁操作 DOM 而導致瀏覽器需要重複 reflow&repaint 的問題,這個過程相當耗費計算資源。
virtual DOM 是 React 用來記錄 DOM tree 上有那些元素、元素分別為什麼狀態的 Object,當 state 改變, React 會透過 diff 演算法,把新舊 virtual DOM 互相比較、篩選出有更動的部分,針對那個部分去進行DOM修改。
所以觸發流程會是長這樣:
```mermaid
graph LR
C[Component] --React--> Vir[Virtual DOM] --diff--> Ch[Change part]--ReactDOM--> D[DOM]
```
其中最耗效能的部位會在:
1. diff 演算法
2. ReactDOM 修改 DOM
由此可知,若要優化效能,我們需要盡量避免:
1. **觸發元件 re-render** → 避免觸發 diff 演算法
2. **盡量保持 virtual DOM 的一致** → 避免觸發 ReactDOM 修改 DOM
# **React 是如何決定 re-render 元件的**
其實比想像中簡單,react 預設就是:
> **當 state 被更動或接受到新的 props,就會觸發 re-render 元件**
>
但這個簡單的原理,碰上 JavaScript 的特性,就不這麼簡單了。
我們都知道,JavaScript 有 `by value` 和 `by reference` 的資料型別差異,前者稱為 **Primitive type (原始型別)**,後者稱作 **Object Type(物件型別)/reference Type(參考型別)**。
React Component 會針對 state 內的值去做嚴格相等(`===`),判斷是否需要 re-render。
當 state 是**原始型別**時,因為比較的是值,所以值不相等才會觸發 re-render。
而若是 object 這類型的**參考型別**,比較的則是 reference,也就是該物件在記憶體中的地址,但由於每一次創建 object 時,都會給定一個新的地址,基本上只要有新的 object 傳入 `setState`,不論裡面的值是否跟上次相同,都會觸發 re-render。
```jsx
//原始型別狀況
const oldState = 1;
const newState = 1;
oldState === newState; //true
//參考型別狀況
const oldState = {id: 1};
const newState = {id: 1};
oldState === newState; //false
```
而為了要觸發 `setState`,我們必須傳入新物件,才可以觸發 `state` 更新,在這種狀況下,無論你的 object 值是否相同,都會因為 reference 改變而觸發 re-render。
而當父層元件 re-render ,子層元件因為是父層元件的一部份,所以即便子層的 props 沒有變動,也一樣會 re-render,例如下面的例子:
```jsx
const Content = () => <div>I'm Content!</div>
const App = () => {
const [state, setState] = useState()
const onClick = () => setState(!state)
return (
<>
<button onClick={onClick}>change!</button>
<Content /> {/* Content 沒有 props 但也一樣會 rerender*/}
</>
)
}
```
# React.memo、useMemo、useCallback
理解了 React 的 render 機制後,React 也提供了幾種方法來避免多餘且昂貴的 re-render。
下面是一個常見的 React 範例,由 App 包裹多個元件,並在這邊控制 state,將 state 傳入子元件的 props,請問:
> 按下 `button` 後,更新 `otherState`,`Table` 是否會被 re-render? [^ReactQuiz]
>
```jsx
const Row = ({item, style}) => (
<tr style={style}>
<td>{item.name}</td>
</tr>
);
const Table = ({list}) => {
const itemStyle = {
color: 'red'
};
return (
<table>
{list.map(item => <Row key={item.name} item={item} style={itemStyle} />)}
</table>
)
};
const App = () => {
const defaultList = Array(10000).fill(0).map((val, index) => ({name: index}));
const [list, setList] = useState(defaultList);
const [otherState, setOtherState] = useState(0);
const onClick = () => {
setOtherState(1)
};
return (
<>
<Table list={list} />
<button onClick={onClick}>change state!</button>
</>
)
};
```
- 答案是:
會!即便 Table 沒有傳入任何 props ,Table 也會因為 App re-render 而導致 re-render。
## React.memo
上述的 `Table` 例子,照理來說因為 props 沒有變化,所以不需要 re-render,React 提供了 `React.memo` 這個方法,來避免這種情形。
`React.memo` 類似 class component 的 pure component,此方法會回傳一個 higher order component,會比較傳進來的 props 是否跟前次呼叫時相同,比較方式則採取淺拷貝(shallow copy)。
使用 React.memo 改寫過後會長這樣:[^ReactQuiz]
```jsx
const Row = ({item, style}) => (
<tr style={style}>
<td>{item.name}</td>
</tr>
);
const Table = React.memo(({list}) => {
const itemStyle = {
color: 'red'
};
return (
<table>
{list.map(item => <Row key={item.name} item={item} style={itemStyle} />)}
</table>
)
});
const App = () => {
const defaultList = Array(10000).fill(0).map((val, index) => ({name: index}));
const [list, setList] = useState(defaultList);
const [otherState, setOtherState] = useState(0);
const onClick = () => {
setOtherState(1)
};
return (
<>
<Table list={list} />
<button onClick={onClick}>change state!</button>
</>
)
};
```
這樣就可以阻止 `Table` 被 re-render了。
那如果換成下面這個例子,點擊 `button` 後的確會改變 `list` 狀態,此時因為 `list` 的確發生變化,要繼續優化的話,我們需要把 `React.memo` 換成包裹住 `Row`。
下一個問題來了:[^ReactQuiz]
> 當 `Table` re-render 時,使用 `React.memo` 包裹的 `Row` 是否達到優化效果?
>
A. 是,有 `React.memo` 的 `Row` 比 沒有的 `Row` 效能更好
B. 否,`React.memo` 包裹的 `Row` 和 沒有的 `Row` 效能差不多
C. 否,`React.memo` 包裹的 `Row` 比起沒有的 `Row` 效能更差
```jsx
const Row = React.memo(({item, style}) => (
<tr style={style}>
<td>{item.name}</td>
</tr>
));
const Table = ({list}) => {
const itemStyle = {
color: 'red'
};
return (
<table>
{list.map(item => <Row key={item.name} item={item} style={itemStyle} />)}
</table>
)
};
const defaultList = Array(10000).fill(0).map((val, index) => ({name: index}));
const App = () => {
const [list, setList] = useState(defaultList);
const [otherState, setOtherState] = useState(0);
const onClick = () => {
setList(prev => [...prev, {name: 123}])
};
return (
<>
<Table list={list} />
<button onClick={onClick}>change state!</button>
</>
)
};
```
答案是:C,但你知道為什麼嗎?
### 問題出在 item ?
上面的例子,我們確定 `Table` 必定會 re-render,因為 list 添加了一個值,上面使用了解構賦值,所以我們知道,`list` 的 reference 必定會改變,那 `list` 中的 `item` 也是一個 object,`item` 的 reference 也會改變嗎?
答案是:不會!因為解構賦值本身是淺拷貝,第二層的 object reference 並不會改變。
所以可以確定這邊的 `item` 跟 `item.name` 都不會變動,那問題會出在哪呢?
```jsx
const Table = ({list}) => {
const itemStyle = {
color: 'red'
};
return (
<table>
{list.map(item => <Row key={item.name} item={item} style={itemStyle} />)}
</table>
)
};
const App = () => {
const defaultList = Array(10000).fill(0).map((val, index) => ({name: index}));
const [list, setList] = useState(defaultList);
const [otherState, setOtherState] = useState(0);
const onClick = () => {
setList(prev => [...prev, {name: 123}])
};
return (
<>
<Table list={list} />
<button onClick={onClick}>change state!</button>
</>
)
};
```
### 問題出在 itemStyle?
你可能會想:怎麼可能!`itemStyle` 是一個寫死的 object ,我連動都沒有動,怎麼可能會造成 re-render?但他的問題就是出在,**`itemStyle` 是個 object**。
當 `Table` re-render 時,定義在 `Table` 作用域的 `itemStyle` 也會被**重新定義。**也就是說,`itemStyle` 即便內容一樣,但他還是換了一個新地址。
這就導致舊的 `Row` 傳入的 `itemStyle` 通通跟之前不一樣,`React.memo` 也會認為 props 的確更新了,進而導致沒有變化的 `Row` 通通 re-render 的情況。
前面已提到,diff 演算法是拖累 React 效能的禍首之一。在這個例子下,`React.memo` 不但沒有達到優化的效果 (有用沒用都一樣會 re-render),反而因為加了 `React.memo` ,React 還必須比較前後 props 有無差異,結果**優化不成反而還加重效能負擔**。
```jsx
const Table = ({list}) => {
const itemStyle = {
color: 'red'
};
return (
<table>
{list.map(item => <Row key={item.name} item={item} style={itemStyle} />)}
</table>
)
};
```
另外也要注意的是,function 也是 reference type,他也會讓元件重渲染時,導致子元件的 `React.memo` 失去作用:
```jsx
const Btn = ({label}) => {
/* re-render 就會重新定義 onClick */
const onClick = () => {
/* do something... */
};
return (
<Button onClick={onClick}>
{label}
</Button>
)
};
const Btn = ({label}) => {
return (
/* 寫在 inline 也會重新定義 onClick function */
<Button onClick={() => {/*do somthing*/}}>
{label}
</Button>
)
}
```
## useMemo & useCallback
先說結論:
> 90% 的情況下你的專案都不需要使用 useMemo & useCallback [^useMemo&Cb]
這兩個 Hook 也是 React 針對效能優化所提供的功能,但他們跟 `React.memo` 不一樣的是,他們是針對 **昂貴的計算結果/function** 進行緩存,只有當 dependency 有所變化時,才會再進行一次重新計算。
**所以 useMemo 跟 useCallback 並不是用來阻止元件 re-render 的!**
你可能會說,不對阿,那我用 useMemo 把值存起來,再傳到子元件裡,下次 re-render 時,props 就不會變動,那元件不就不會 re-render 了嗎?
No, No, No,要是這樣我就不用寫這麼多了~
根據上面這個假設,你的 code 大概會長的像這樣:
```jsx
// 1. 利用 useCallback 記憶 onClick 想避免 button re-render
const Component = () => {
const onClick = useCallback(
() => { /* do something */ }, []
);
return (
<>
<button onClick={onClick}>Click me</button>
... // 其他元件
</>
);
};
// 2. useCallback 依賴 value,所以為了避免 onClick 重新被定義,value 使用 useMemo 緩存
const Item = ({ item, onClick }) => <button onClick={onClick}>
{item.name}
</button>;
const Component = ({ data }) => {
const [someStateValue, setSomeStateValue] = useState();
const value = useMemo(() => ({ a: someStateValue }), [someStateValue]);
const onClick = useCallback(() => { console.log(value); }, [value]);
return (
<>
{data.map((d) => (<Item item={d} onClick={onClick} />))}
</>
);
};
```
如果你曾經幹過這種事,恭喜你,上述的優化**通通沒作用**!不只如此,還因為多了一堆比較跟緩存拖慢效能,code 也變得更複雜難解。
上述範例的邏輯建立在下面這個前提:
> **只要 props 不改變,元件就不會re-render**
>
嘿!還記得前面說 React 怎麼觸發元件 re-render 嗎?我們再來複習一下:
> **當 state 被更動或接受到新的 props,就會觸發 re-render 元件**
>
React 只說了這個情況會觸發 re-render,但是這並不代表,只要 props 不改變,元件就不會 re-render。上面這個假設,實際上是落入了典型的邏輯錯誤, A 是 B 的充分條件,但 !A 不代表是 !B 的充分條件[^useMemo&Cb]。
事實上,只要碰上了**父元件 re-render**,不管你的 props 有沒有改,甚至是沒有 props ,**子元件通通都得 re-render**!
而 React 提供真正能阻止子元件 re-render 的方法只有 `React.memo` 。父元件渲染子元件時,若碰到 `React.memo` 才會中斷子元件的 render,確定 props 有修改,才會繼續 render。
所以 `useMemo` 跟 `useCallback` 若要真正阻止子元件重新渲染,必須搭配 `React.memo`,就像這樣:
```jsx
// React.memo 會中斷 re-render 程序,判斷是否需要繼續渲染
const Item = React.memo(({ item, onClick }) => <button onClick={onClick}>
{item.name}
</button>);
const Component = ({ data }) => {
const [someStateValue, setSomeStateValue] = useState();
// value 是 onClick 的依賴項,所以也必須緩存
const value = useMemo(() => ({ a: someStateValue }), [someStateValue]);
// onClick 必須被緩存,否則每次 Component 重渲染 onClick 就會被重新定義,導致 React.memo 無作用
const onClick = useCallback(() => { console.log(value); }, [value]);
return (
<>
{data.map((d) => (<Item item={d} onClick={onClick} />))}
</>
);
};
```
也只有在搭配 `React.memo` 的狀態下,使用 `useCallback` 與 `useMemo` 來做 props 的緩存才有意義。**但是你做這樣子的優化付出的代價也相當巨大**。
前面也有提到,React 最花效能的地方是在 **diff 演算法**及 **render DOM** 的步驟,大部分的情況下,記憶這些數值/function 相比之下並沒有太大的優化效果。
大量運算大多會集中在迴圈相關的操作,[這篇文章](https://cloud.tencent.com/developer/article/1967299)針對 useMemo 能優化多少效能做了實驗。
從下面這張圖可以看到,useMemo 雖然在 re-render 階段會優化效能,但只有在 array 長度 > 1000 才有顯著效益,但 useMemo 所帶來的 initial render 的負擔也是非常巨大,array 的長度在 1000 以下,基本上根本不需要 useMemo。

*useMemo mount 效能會更差,在 re-render 時才有改善效能的作用* [^useMemo]
況且如果真的需要做這麼大量的計算,也應該要考慮,這些計算在前端做是否合理?
## 小結
經由上面的內容,我們應該要知道,當使用 React.memo、useMemo、useCallback 作為優化的工具時:
- `useMemo` 和 `useCallback` 是拿來**緩存**計算數值、function 的工具,並不是使用了 `useMemo` 或 `useCallback` 就可以保證元件不會 re-render
- 在一個大前提下:**父元件重新渲染**,除了 `React.memo` ,上述兩個 hook ,對避免 re-render **都不管用**。
- `React.memo` 會在 props 改變前提下重新渲染,因此如果有個元件如下,在 `onClick` 未被 `useCallback` 緩存時,即使這個元件使用了 `React.memo` ,也會因為父元件重新渲染時,又重新定義了 `onClick` 這個 function ,而重新渲染。
```jsx
const Btn = ({onClick}) => React.memo(<Button onClick={onClick} />);
const Container = () => {
const [isChange, setIsChange] = useState(false);
const onClick = () => {
setIsChange(!isChange);
};
return <Btn onClick={onClick} />
;
```
- `useMemo` 和 `useCallback` 只有在 re-render 的時候有優化效用,**初始渲染**下,他們不只無用,還可能拖慢 React 的工作,也就是說,若是在不當的使用下,使用這兩個 hook 反而會拖慢效能。
- 使用 `React.memo`、`useMemo`、`useCallback` 都必須謹慎,當你認為你必須使用時,都應該考慮到使用這些方法是在**拿你的記憶體去跟效能做交易**。
# 不需要上述 React 優化函式的優化方案
由前面的敘述可知,state 和 props 變更是觸發 render 的關鍵要素之外,父元件的 re-render 也是造成子元件 re-render 的主要原因,也就是說,透過設計,就算不使用上述 React 提供的優化函式,只要把握不讓 state、props 改變,以及避免父元件 re-render ,也可以達到效能優化的目的。
下面整理出幾種可操作的方式:
## 讓 state 只影響他該影響的範圍
此方法的重點在於,避免 state 影響到其他不相關的元件,比如上面看過的例子:
```jsx
const Content = () => <div>I'm Content!</div>
const App = () => {
const [state, setState] = useState()
const onClick = () => setState(!state)
return (
<>
<button onClick={onClick}>change!</button>
<Content /> {/* Content 沒有 props 但也一樣會 rerender*/}
</>
)
}
```
`Content` 沒有 props 但卻會因為 `state` 變化而重新渲染。此時除了使用 `React.memo` ,你還可以將 `button` 拆成新元件 `Btn`,將 `state` 移到 `Btn` 裡面,如此一來,`state` 只會影響到 `button` 的範圍:
```jsx
const Content = () => <div>I'm Content!</div>
const Btn = ({children, ...props}) => {
const [state, setState] = useState();
const onClick = () => setState(!state);
return <button onClick={onClick} {...props}>{children}</button>
};
const App = () => {
return (
<>
<Btn>change!</Btn>
<Content /> {/* App 不會 re-render,Content 也不會 */}
</>
)
};
```
## Children props
前面提到 React 的父層元件 re-render 的同時,底下的所有子元件都會跟著 re-render,React.memo 則是為了阻止 prop 沒有改變的子元件也跟著 re-render 所產生出來的解法。但其實我們也可以利用 children props 來阻斷這個 re-render 的連鎖效應。
作法也很簡單,只要將不想受影響的子元件獨立出來,在祖父元件裡使用 children props 傳到原本的父元件即可:
```jsx
const Child = () => <p>Hello World!</p>
const Parent = ({children}) => {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1 )}>{count}</button>
{children}
</>
)
}
const GrantParent = () => {
return (
<Parent>
<Child />
</Parent>
)
}
```
這個方法不需要 React 記憶 props、比對新舊 props 的差異,只需要調整結構就可以達到避免重渲染的效果。
這個方法也可以優化使用 Context 所影響的子元件 re-render 問題,如果使用 Context 原本的 Code 可能會長這樣[^childrenProps]:
```jsx
import { useState, useContext, createContext } from 'react';
const Context = createContext();
const ChildWithCount = () => {
const { count, setCount } = useContext(Context);
console.log('ChildWithCount re-renders');
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<p>Child</p>
</div>
);
};
const ExpensiveChild = () => {
console.log('ExpensiveChild re-renders');
return <p>Expensive child</p>;
};
const CountContext = () => {
const [count, setCount] = useState(0);
const contextValue = { count, setCount };
return (
<Context.Provider value={contextValue}>
<ChildWithCount />
<ExpensiveChild /> // Imagine re-rendering this component is expensive
</Context.Provider>
);
};
const App = () => {
return <CountContext />;
};
export default App;
```
在這種情況下,CountContext 下面的 ChildWithCount 從 context 接收到 setCount ,修改 count 則會導致 CountContext state 改變而造成 re-render,這邊也可以使用 children props 來改善:
```jsx
import { useState, useContext, createContext } from 'react';
const Context = createContext();
const ChildWithCount = () => {
const { count, setCount } = useContext(Context);
console.log('ChildWithCount re-renders');
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<p>Child</p>
</div>
);
};
const ExpensiveChild = () => {
console.log('ExpensiveChild re-renders');
return <p>Expensive child</p>;
};
const CountContext = ({ children }) => {
const [count, setCount] = useState(0);
const contextValue = { count, setCount };
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
};
const App = () => {
return (
<CountContext>
<ChildWithCount />
<ExpensiveChild />
</CountContext>
);
};
export default App;
```
## React Key
有沒有搞錯 React Key 也可以拿來優化效能?的確 React Key 並不是為了優化效能而產生的,但我們可以利用它的特性來達成優化的目的。
首先,我們必須先搞懂 React Key 的工作機制。
### React Key 的運作原理
在官方文檔中,Key 的描述是寫在 [Rendering List](https://react.dev/learn/rendering-lists#rules-of-keys) 這一章節,在開發過程中,使用 array 形式的 children 也通常會被 console 中的警告要求帶入 `unique “key” prop` ,官方說明使用 keys 有兩個原則:
> - **Keys must be unique among siblings.** However, it’s okay to use the same keys for JSX nodes in *different* arrays.
> *在兄弟元素之間 Key 必須保持獨特。但是如果是不同的 JSX array 使用相同的 Key 是可以的。*
>
> - **Keys must not change** or that defeats their purpose! Don’t generate them while rendering.
> *Key 一旦設定就不可以改變,否則會破壞使用 Key 的初衷。請避免在渲染時重新產生 key。*
>
根據官方文件,使用 Key 的目的是,讓 React 可以從一堆類似的元件中辨識元件,而不是根據他的順序來辨認。React 靠 Key 來辨識元件是否有所更動,進而針對 DOM 進行操作。
前面也提過操作 DOM 是最耗費效能的操作之一。Key 其實是用來幫助 React 優化效能的標誌。
當你沒有設定獨特的 Key 時,React 會自動默認 Array 的 index 當作 key,但 index 會在針對 array 進行排序、插入、刪除時產生變化,React 可能會把分明不相同的元素視為是同一個元素,造成不必要的 re-render,這也是為什麼我們要避免使用 index 當作 Key 的原因之一。

*元件只是換了順序,元件本身其實不需要 re-render,但因為 React 將 index 相同的元件視為同個元件,認為該元件的值 (Australia ⇒ UK, UK ⇒ Australia) 產生了變化,導致全部元件都 re-render* [^ReactKey]

*使用唯一值時,component 都不會 re-render,只是更換順序* [^ReactKey]
同樣的,隨機產生的 key 也應該避免使用。這會讓 React 無法辨識元件,造成每次 render 都會把原本就的元素刪掉再重新創建元素,即便他們實際上沒有改變。這甚至可能比什麼都不加更糟。

*component 使用 index 做為 key,只會觸發 re-render* [^ReactKey]

*使用隨機值作為 key 將永遠不會觸發 re-render* [^ReactKey]
總和來說,React re-render 的過程大致上會是這樣:
1. React 生成元素「之前」和「之後」的快照
2. React 識別頁面中已存在的元素,以便重新使用他們,而非重新創建
1. 如果 `key` 存在,React 會將 「之前」和「之後」元素中,`key` 值相同的元素視為同一個元素
2. 如果 `key` 不存在,React 會默認 index 為 `key`
3. React 辨識完元素後,會開始進行:
1. `unmount` : 刪除 re-render 「之前」存在,但「之後」不存在的元素
2. `mount` : 創建「之前」不存在的元素
3. `re-render`:更新「之前」和「之後」都存在的元素
### React Key 要怎麼拿來優化效能?
前面我們知道 key 是 React 拿來判斷要將元件 re-render 或 mount 的基準,所以我們可以利用這個特性,讓 React 用 re-render 取代元素被卸載又被重建的行為。
上面提到,使用 index 當作 key 有可能造成不必要的 re-render,但假設你的 array 數量固定、排序不變的狀況下,使用 index 作為 key 會比使用 unique 的 key 來的更好,例如分頁列表就是屬於這種形式:

由於每一頁的資料都固定是 12 筆,如果使用的是 unique key ,當分頁切換時,第一頁的 12 個商品 component 都會被卸載,並且重新創建第二頁的 12 個商品項目。
但如果使用 index 當作 key,第一頁載入所創建的 12 個商品元件不會被卸載,而是會被當作是資料變更,進行將第二頁資料傳入的 re-render 形式,相比卸載又加載,re-render 更加節省效能。
總結一下,使用 react key 的幾個要點:
- **不使用隨機值作為 key** ⇒ 將導致元件永遠都需要重複卸載&加載
- 在列表順序、數量皆保持不變的 **「靜態」列表,使用 index 作為 key** ⇒ 避免複雜元件重複卸載&加載
- 可添加、刪除或修改順序的 **「動態」列表,使用 unique key** ⇒ 可避免不需要的 re-render
# Recap
- **優化 React 效能的大方向有兩個**:
1. 避免元件 re-render 觸發 diff 演算法,也就是需要避免 state 與 props 的變動
2. 盡量保持 virtualDOM 的一致,避免 ReactDOM 操作 DOM,也就是需要避免重複加載 & 卸載
- **為了提升效能,React 內建提供的優化方法有**:
- **React.memo**
會記憶元件的 props,在 re-render 時比較新舊 props 是否有異動,有異動才繼續 re-render 元件。
- **useMemo**
拿來緩存昂貴的計算使用,只在 dependency 變更的狀況下才重新執行,並不能阻止使用該值的元件 re-render。
- **useCallback**
跟 useMemo 類似,拿來緩存在元件內的 callback function,並且只在 dependency 變更的狀況下才重新定義。但一樣不能阻止使用該值的元件 re-render,必須搭配 React.memo 才有效果。
👉 以上 3 種方法都需要 React 撥出額外的記憶體去記憶 & 比較 dependency,使用時必須記得:
> **每當使用以上方法,都是在拿你的記憶體跟效能做交易**
- **而根據 React 優化的大方向來說,也有不需要使用 React 提供的 function 的方法有**:
1. 調整 component 結構,避免被不相干的 state 影響元件 re-render
- 移動 state,讓 state 只存在在需要使用的元件內
- 活用 children props,將不受 state 影響的元件,以 children props 的方式傳入該元件
2. 不使用隨機值作為 key,並可活用 React key 的特性:
- 「靜態列表」(數量排序不變,僅內容變化) 使用 **index** key ,可避免重複卸載與加載 (僅 re-render)
- 「動態列表」(可增加、刪減、調整排序) 使用 **unique** key,可減少不必要的 re-render
# 參考&引用資料
[^childrenProps]:[React components - when do children re-render?](https://whereisthemouse.com/react-components-when-do-children-re-render)
[^ReactQuiz]:[React 性能優化大挑戰:一次理解 Immutable data 跟 shouldComponentUpdate](https://blog.huli.tw/2018/01/15/react-performance-immutable-and-scu/)
[^useMemo]:[【译】你真的应该使用useMemo吗? 让我们一起来看看 - 掘金](https://juejin.cn/post/6969874579028197413?searchId=202311020934016CB13EE5CB8BE9F008DC)
[^useMemo&Cb]:[「好文翻译」为什么你可以删除 90% 的 useMemo 和 useCallback ? - 掘金](https://juejin.cn/post/7251802404877893689)
[^ReactKey]:[「好文翻译」React key属性:高性能列表的最佳实践 - 掘金](https://juejin.cn/post/7257022428194521145)