前言
在 react 中,组件渲染的是最常有的事情。但是,有部分的渲染是不必要的,是可以避免的。
在 react 的一般规则中,只有父组件的某一个状态改变,父组件下面所有的子组件不论是否使用了该状态,都会进行重新渲染。
显然,对于没有用到被改变的那个状态的组件来说,重新渲染是完全没有必要的。所以,React.memo 就诞生了。
父组件中状态的改变会让所有的子组件重新渲染
举个例子 ↓
上面的例子中,我们有两个 state,一个 buibuibui,一个 tututu。被传入 children 组件的是 tututu 状态,在父组件中改变的是 buibuibui 状态。
问:当父组件的 buibuibui 这个 state 被改变的时候,只接收了 tututu 这个变量的 Children 组件(子组件)于 buibuibui 状态并无关联,Children 组件会被重新渲染吗。
答:会的,只要父组件的状态改变,所有的子组件不论是否使用到了被改变的那个 state 都会被重新渲染。
如图 ↓
显然这种渲染是完全没必要的。我又没有使用被改变的那个 state,我本身本身也没有什么视图需要更新,根本没必要重新渲染。
在阻止重新渲染这个需求的基础上,诞生了memo(),memo 是 react 的一种缓存技术,这个函数可以检测从父组件接收的 props,并且在父组件改变 state 的时候比对这个 state 是否是本组件在使用,如果不是,则拒绝重新渲染。
memo 和它的使用方式
并且它的使用方法也非常的简单。只需要在把子组件当成这个函数的入参包起来就好了。
例如 ↓
像这样,这 Children 就是被缓存成功了。下次当父组件中无关它的 state(状态)被更新时候,Children 组件就不会重新渲染。
参考上面的例子去介绍 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 变了,它也不会重新渲染.
例如 ↓
上图中,传入了一个 list 数组进去子组件,子组件内部是被 memo 缓存了的。这个时候,如果我们往 list 这个数组中 push()一个 6,那么子组件中的 props 改变了,理论上来说,子组件应该重新渲染了。但实际上并不会。
这是为什么呢?因为 memo 的保护是对 props 做一个浅比较(不了解什么是浅比较的同学点这里)
而数组的使用 push()方法看似是变了。但变的只是堆中的数据,存在与栈中的地址依然不会改变。memo 是检测不到的。所以,使用 push 等不能返回一个新数组的方法,均无法触发 memo 的更新机制。
如图 ↓
想要改变数组也能让子组件更新,有方法。 就像刚刚说的,需要让 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 不是项目中所有的组件都需要包一下。包的太多反而会起反效果,我们需要选择那些经常被重新渲染的组件有选择性的去缓存。
作者:工边页字 链接:https://juejin.cn/post/7108659932542533639 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。