奇怪的useMemo知识增加了

开发 前端
这篇文章提到的useMemo用法,相比Vue,React更灵活,开发过程中需要开发者注意更多细节。要完全了解React,可能需要学习一些源码层面的知识。

[[380079]]

 作为「性能优化」手段,一般用useMemo缓存函数组件中比较消耗性能的计算结果:

  1. function App() { 
  2.   const memoizedValue = useMemo( 
  3.     () => computeExpensiveValue(a, b), 
  4.     [a, b] 
  5.   ); 
  6.   // ... 

只有在依赖项改变后才会重新计算新的memoizedValue。

你有没有想过,如果用useMemo缓存函数组件的返回值,会怎么样呢?

举个例子

我们有个全局context —— AppContext。

由于同学们偷懒,随着项目的迭代,新增的context都选择放在AppContext里,导致AppContext包含的内容越来越多。

现在我们有个Tree组件,他会渲染一个很耗性能的大组件ExpensiveTree。

  1. function Tree() { 
  2.   let appContextValue = useContext(AppContext); 
  3.   let theme = appContextValue.theme; 
  4.  
  5.   return <ExpensiveTree className={theme} />; 

该组件内部依赖AppContext中的theme状态。

由于AppContext中包含很多与theme无关的state,导致每次其他无关的state更新,Tree都会重新render,进而ExpensiveTree组件也重新render。

现在这个优化任务交到了你手上,该怎么办呢?

优化ExpensiveTree

这时候,useMemo就能派上用场:

  1. function Tree() { 
  2.   let appContextValue = useContext(AppContext); 
  3.   let theme = appContextValue.theme; 
  4.  
  5.   return useMemo(() => { 
  6.     return <ExpensiveTree className={theme} />; 
  7.   }, [theme]) 

我们将返回的ExpensiveTree作为useMemo返回值,theme作为依赖。

这样,即使AppContext改变导致Tree反复render,ExpensiveTree也只会在theme改变后render。

[[380080]]

原理解析

要理解这么做有效的原因,需要了解三点:

  1. useMemo返回值是什么
  2. 函数组件的返回值是什么
  3. 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

 

责任编辑:姜华 来源: 魔术师卡颂
相关推荐

2021-10-09 09:35:28

开发JavaScript 代码

2021-02-22 10:23:01

程序员技能开发者

2010-10-19 11:30:16

IT培训

2023-01-16 18:22:53

Discourse开源

2009-06-11 17:18:23

EJB3.1Singleton B

2012-04-30 20:44:55

2022-02-10 10:52:09

网络攻击网络安全漏洞

2023-05-21 23:40:03

开源图文模型

2015-10-19 17:36:19

MOST内核Linux

2020-05-15 19:25:09

HTML5JavaScript前端

2020-12-29 09:37:41

漏洞网络安全网络攻击

2022-05-30 16:19:40

恶意软件僵尸网络网络攻击

2009-01-12 16:25:40

电子邮件数据管理法规遵从

2023-10-04 09:38:55

Firefox浏览器

2023-12-15 14:57:39

ReactNativeFabric

2022-11-04 12:27:35

2021-03-18 08:03:58

SteamMesa缓存

2018-07-11 04:16:16

2022-08-08 10:42:39

物联网物联网技术

2018-09-25 10:46:15

点赞
收藏

51CTO技术栈公众号