react性能优化篇之memo的作用和memo的细节讲解

2,240 阅读6分钟

本人已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

在react中,组件渲染的是最常有的事情。但是,有部分的渲染是不必要的,是可以避免的。

在react的一般规则中,只有父组件的某一个状态改变,父组件下面所有的子组件不论是否使用了该状态,都会进行重新渲染。

显然,对于没有用到被改变的那个状态的组件来说,重新渲染是完全没有必要的。所以,React.memo就诞生了。

父组件中状态的改变会让所有的子组件重新渲染

举个例子 ↓

image.png

image.png 上面的例子中,我们有两个state,一个buibuibui,一个tututu。被传入children组件的是tututu状态,在父组件中改变的是buibuibui状态。

问:当父组件的buibuibui这个state被改变的时候,只接收了tututu这个变量的Children组件(子组件)于buibuibui状态并无关联,Children组件会被重新渲染吗。

答:会的,只要父组件的状态改变,所有的子组件不论是否使用到了被改变的那个state都会被重新渲染。

如图↓

image.png

显然这种渲染是完全没必要的。我又没有使用被改变的那个state,我本身本身也没有什么视图需要更新,根本没必要重新渲染。

在阻止重新渲染这个需求的基础上,诞生了memo()memo是react的一种缓存技术,这个函数可以检测从父组件接收的props,并且在父组件改变state的时候比对这个state是否是本组件在使用,如果不是,则拒绝重新渲染。

memo 和它的使用方式

并且它的使用方法也非常的简单。只需要在把子组件当成这个函数的入参包起来就好了。

例如↓

image.png

像这样,这Children就是被缓存成功了。下次当父组件中无关它的state(状态)被更新时候,Children组件就不会重新渲染。

image.png

参考上面的例子去介绍memo()就是

子组件被memo函数保护了。当父组件中一切无关子组件的state被改变时,子组件拒绝重新渲染,这样节省了性能。

而上面的例子中,传入Children组件的是tututu这个状态。而父组件中被改变的是buibuibui这个state。buibuibui被改变与Children无关,因为children被memo保护,所以chidren不重新渲染

反之,如果children组件没有被memo保护,那么即使被改变的buibuibui这个state与children组件无关。children组件也会被重新渲染。

总而言之就是,如果当前组件被memo保护,那么当前组件的props不变,则组件不进行重新渲染。这样,我们合理的使用memo就可以为我们的项目带来很大的性能优化。

以上,就是关于react的组价缓存。

memo的坑(细节讲解)

上面说了memo的用处和好处。 下面我们来说memo坑的地方

第一个坑

有那么一种情况,当被改变的那个props是一个数组(对象)的时候,被memo的保护的组件即使props变了,它也不会重新渲染**。**

例如↓

image.png

image.png

上图中,传入了一个list数组进去子组件,子组件内部是被memo缓存了的。这个时候,如果我们往list这个数组中push()一个6,那么子组件中的props改变了,理论上来说,子组件应该重新渲染了。但实际上并不会。

这是为什么呢?因为memo的保护是对props做一个浅比较不了解什么是浅比较的同学点这里

而数组的使用push()方法看似是变了。但变的只是堆中的数据,存在与栈中的地址依然不会改变。memo是检测不到的。所以,使用push等不能返回一个新数组的方法,均无法触发memo的更新机制。

如图↓

image.png

想要改变数组也能让子组件更新,有方法。 就像刚刚说的,需要让memo检测到数组栈地址的变化。要栈地址变化的话,只要返回一个全新的数组就好了。

所以,我们不妨将代码做出以下修改。

	const [list,setList] = useState([1,2,3,4,5]);
	
	setList(list.push(1)); //这样是不会被memo检测到的,是无法触发memo更新的
	
	setList([...list,1]); //这样才可以,创建一个新数组,再在里面解构旧数组,往后面追加 1
	//这样,就等于返回了一个新的数组,栈中的地址就会改变,memo就可以检测到并触发更新

这样,就可以既修改数组,又触发组件更新了

第二个坑

对!还有第二个坑!!

memo是不是很好用?是不是有那么好的东西恨不得每个组件都包一下?

这就是它坑的地方了,它不能每个组件都包一下

倒不是说全包就报错,只是如果全包的话,还不如不包。

话说回来,如果真的每个组件都有被缓存的必要而且不会给项目带来破坏性问题,为什么react不直接把memo设为默认的呢。当然是因为每个都缓存的话,会给项目带来毁灭性的问题

我们需要知道的是,缓存也需要成本。如果每个组件都进行缓存,会给浏览器带来非常非常大的负担。

所以在平常项目中,我们需要挑选一些经常被使用,经常会被重新渲染的组件去有目标的缓存他。而不是每一个组件都缓存一下

总结

  • 父组件中state(状态)改变,不受memo保护的子组件也会重新渲染
  • memo会检测props到改变来决定组件是否需要进行重新渲染,换言之就是,被memo函数包起来的组件只有本身的props被改变之后才会重新渲染
  • memo只能进行浅拷贝来校验决定是否触发重新渲染。所以改变数组(对象)的props时候记得返回一个全新的数组(对象)
  • memo不是项目中所有的组件都需要包一下。包的太多反而会起反效果,我们需要选择那些经常被重新渲染的组件有选择性的去缓存。