奇怪的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
:
oldProps === newProps
前后两次更新props
全等,注意是「全等」。
组件
context
没有变化workInProgress.type === current.type
组件更新前后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
,可能需要学习一些源码层面的知识。
参考资料
#15156: https://github.com/facebook/react/issues/15156#issuecomment-474590693