奇怪的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 ; }
该组件内部依赖 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 ; }, [theme]) }
我们将返回的 ExpensiveTree
作为 useMemo
返回值, theme
作为依赖。
这样,即使 AppContext
改变导致 Tree
反复 render
, ExpensiveTree
也只会在 theme
改变后 render
。

原理解析
要理解这么做有效的原因,需要了解三点:
-
useMemo
返回值是什么 - 函数组件的返回值是什么
-
React
组件在什么时候render
回答第一个问题: useMemo
会将第一个参数(函数)的返回值保存在组件对应 fiber
中,只有在依赖项(第二个参数)变化后才会重新调用第一个参数(函数)计算一个新值。
回答第二个问题:函数组件的返回值是 JSX
对象。
同一个函数组件调用多次,返回的是多个 「不同」 的 JSX
对象(即使 props
未变,但 JSX
是新的引用)。
按照以上两个回答,我们可以得出结论:
以上 useMemo
用法实际上在函数组件对应的 fiber
中缓存了一个完整的 JSX
对象
第三个问题,函数组件需要同时满足如下条件才不会 render
:
- oldProps === newProps
前后两次更新 props
全等,注意是 「全等」 。
context
组件更新前后 fiber.type
未变化,比如 div
没有变为 p
。
- !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
不变,每次 Tree
render
后返回的都是同一个 JSX
对象,满足第一条。
基于这个原因, ExpensiveTree
不会 render
。
总结
这篇文章提到的 useMemo
用法,并未在官网文档中体现,而是在 #15156 [1]中由 Dan
介绍。
相比 Vue
, React
更灵活,开发过程中需要开发者注意更多细节。要完全了解 React
,可能需要学习一些源码层面的知识。
参考资料
[1]
#15156: https://github.com/facebook/react/issues/15156#issuecomment-474590693