奇怪的useMemo知識增加了
作為「性能優化」手段,一般用useMemo緩存函數組件中比較消耗性能的計算結果:
- function App() {
- const memoizedValue = useMemo(
- () => computeExpensiveValue(a, b),
- [a, b]
- );
- // ...
- }
只有在依賴項改變后才會重新計算新的memoizedValue。
你有沒有想過,如果用useMemo緩存函數組件的返回值,會怎么樣呢?
舉個例子
我們有個全局context —— AppContext。
由于同學們偷懶,隨著項目的迭代,新增的context都選擇放在AppContext里,導致AppContext包含的內容越來越多。
現在我們有個Tree組件,他會渲染一個很耗性能的大組件ExpensiveTree。
- function Tree() {
- let appContextValue = useContext(AppContext);
- let theme = appContextValue.theme;
- return <ExpensiveTree className={theme} />;
- }
該組件內部依賴AppContext中的theme狀態。
由于AppContext中包含很多與theme無關的state,導致每次其他無關的state更新,Tree都會重新render,進而ExpensiveTree組件也重新render。
現在這個優化任務交到了你手上,該怎么辦呢?
優化ExpensiveTree
這時候,useMemo就能派上用場:
- function Tree() {
- let appContextValue = useContext(AppContext);
- let theme = appContextValue.theme;
- return useMemo(() => {
- return <ExpensiveTree className={theme} />;
- }, [theme])
- }
我們將返回的ExpensiveTree作為useMemo返回值,theme作為依賴。
這樣,即使AppContext改變導致Tree反復render,ExpensiveTree也只會在theme改變后render。
原理解析
要理解這么做有效的原因,需要了解三點:
- useMemo返回值是什么
- 函數組件的返回值是什么
- React組件在什么時候render
回答第一個問題:useMemo會將第一個參數(函數)的返回值保存在組件對應fiber中,只有在依賴項(第二個參數)變化后才會重新調用第一個參數(函數)計算一個新值。
回答第二個問題:函數組件的返回值是JSX對象。
同一個函數組件調用多次,返回的是多個「不同」的JSX對象(即使props未變,但JSX是新的引用)。
按照以上兩個回答,我們可以得出結論:
- 以上useMemo用法實際上在函數組件對應的fiber中緩存了一個完整的JSX對象
第三個問題,函數組件需要同時滿足如下條件才不會render:
1 oldProps === newProps
前后兩次更新props全等,注意是「全等」。
2 組件context沒有變化
3 workInProgress.type === current.type
組件更新前后fiber.type未變化,比如div沒有變為p。
4 !includesSomeLane(renderLanes, updateLanes)
當前fiber上不存在更新,或者存在更新但優先級低。
- 更詳細的解釋,可以參考這篇文章:React組件到底什么時候render?
當我們不使用useMemo包裹返回值,每次Tree render返回的都是全新的JSX對象。
所以對于ExpensiveTree,oldProps !== newProps。
再看2:ExpensiveTree內部context沒變,滿足
再看3:ExpensiveTree更新前后type都是ExpensiveTree,滿足
再看4: ExpensiveTree內沒有狀態更新,滿足
所以,當我們使用useMemo包裹ExpensiveTree后,當theme不變,每次Treerender后返回的都是同一個JSX對象,滿足第一條。
基于這個原因,ExpensiveTree不會render。
總結
這篇文章提到的useMemo用法,并未在官網文檔中體現,而是在#15156[1]中由Dan介紹。
相比Vue,React更靈活,開發過程中需要開發者注意更多細節。要完全了解React,可能需要學習一些源碼層面的知識。
參考資料
[1]#15156:
https://github.com/facebook/react/issues/15156#issuecomment-474590693