最近都在看這位開發者的文章、她寫的文章都很深入又淺顯易懂,這篇主要是彙整跟React效能有關的文章筆記,以及一些避免降低效能的模式(pattern),如果之後有看到其他的應該會陸續補上。
Re-render 重新渲染
作者提到大部分的React performance議題大概都跟「重新渲染(re-render)」有關,而React元件(component)重新渲染的時機如下:
- 自己的state、props或context改變會導致重新渲染
- 父(母)元件重新渲染,子元件沒做任何措施的話一定會跟著重新渲染
另外,React是採用嚴格比較(strict comparison)來判斷前次和這次渲染的變數是否相同,但React的元件本質上是函式,可以用JavaScript的觀點想成每次重新呼叫函式,函式內的區域變數(local variables)都會重新建立,尤其是物件(object) ─ 物件會要一塊新的記憶體儲存看起來相同的內容;同理,元件每次重新渲染,元件內的變數都會重新建立,而且要注意物件雖然看起來跟前一次渲染一樣,但其記憶體位址不同,在嚴格比較下就和前一次渲染的物件不同。
Anti-Pattern
歸納這篇文章提到的幾個不好的設計如下:
把一個元件定義在另一個元件內 ─ 這會導致裡面的元件會被重新建立並且重新掛載(re-mount)到DOM(因為元件其實是函式物件!)
function Parent(){ function Child(){ return <div>child</div> } return <Child /> }
使用useContext的value是物件(object):如下
Provider
的value其實是物件{context}
function Parent(){ return <Context.Provider value={{context}}> <button onClick={()=>console.log('重新渲染')}>重新渲染</button> </Context.Provider> }
把子元件的props用
useMemo
、useCallback
包起來 ─ 若子元件的父(母)元件重新渲染,即使子元件的props相同,仍舊會跟著父(母)元件重新渲染:如下圖<Child/>
的(e)=>setChildState
若直接寫在onClick={}
會因為每次都會建立新的函式物件導致<Child/>
本身重新渲染,所以範例用useCallback
把函式包起來;不過若是parentState
改變導致<Parent />
重新渲染,<Child/>
的handleChildClick
雖然沒有改變但仍舊會跟著<Parent />
一起重新渲染。function Parent(){ const [parentState, setParentState] = useState([]); const [childState, setChildState] = useState([]); const handleChildClick = useCallback((e)=>console.log(e), []); return <Child childState={childState} onClick={handleChildClick}> }
有關前面提到的不良設計有幾個解決方法:
元件定義在其他元件以外;
function Child(){ return <div>child</div> } function Parent(){ return <Child /> }
物件型態的state、props或context用
useMemo
包起來;function Parent(){ const memoContext = useMemo(() => {context}, []) return <Context.Provider value={memoContext}> <button onClick={()=>console.log('重新渲染')}>重新渲染</button> </Context.Provider> }
子元件用
useMemo
包起來。function Parent(){ const [parentState, setParentState] = useState([]); const [childState, setChildState] = useState([]); const child = useMemo( ()=> <Child childState={childState} onClick={(e)=>console.log(e)}> , [childState]); return <>{child}</> }
但其實除了以上方法也有其他避免父(母)元件重新渲染導致子元件重新渲染的方法,例如:
使用children
function Provider(){ return <Context.Provider value={{context}}> {children} </Context.Provider> } function Parent(){ return <Provider> <button onClick={()=>console.log('重新渲染')}>重新渲染</button> </Provider> }
把子元件用
React.memo
包起來function Parent(){ const [parentState, setParentState] = useState([]); const [childState, setChildState] = useState([]); const handleChildClick = useCallback((e)=>setChildState, []); return React.memo(<Child childState={childState} onClick={handleChildClick}>); }
References
How to write performant React code: rules, patterns, do's and don'ts
Before You memo()