memo,useMemo,useCallback之间的关联
创建人 | 内容 | 创建时间 | 更新时间 |
---|---|---|---|
adsionli | memo,useMemo,useCallback之间的关联 | 2023.04.20 | 2023.04.20 |
在React中,我们可以主动的进行渲染控制以减少不必要的重复渲染开销,可以通过使用memo,useMemo,useCallback
这三个React提供的内容。
但是在使用memo,useMemo,useCallback
之前,我们必须要保证,在我们缓存的组件不能够出现会影响缓存状态的东西,比如在待缓存的组件中使用useContext
等内容,我们可以将context
的内容通过props
的形式传入到缓存组件中。这样做的原因是因为我们一旦在memo
缓存组件中使用了useContext
来获取上下文状态时,就会破化缓存性,导致其必然会受到重新渲染的问题。
这里被破化的情况在react官网有提到
memo
memo
的作用是在子组件接受的props
未发生改变时跳过重新渲染的一个高阶组件,它通常会和useMemo
与useCallback
进行配合使用。
参数说明
Component
: 需要被缓存的组件,memo
不会修改组件内容,只会返回一个新的记忆化组件。arePropsEqual
: 可选参数,用于协助判断组件前后的props是否相等,可以接受两个参数:prevProps
与newProps
,如果prevProps == newProps
,需要返回true
,即组件渲染输出结果相同且newProps
造成的行为也完全相同。(主要是为了进行更深层次的比较,因为本身的比较是浅比较,所以为了能够更好的控制渲染更新,可自定深比较)
返回值
memo 返回一个新的 React 组件。它的行为与提供给 memo 的组件相同,除了 React 不会总是在其父级被重新渲染时重新渲染它,除非它的props发生了变化。
使用memo时需要注意的点:
- 当使用
memo
后返回的组件本身具有的state
改变后,组件会触发重新渲染。 - 如果
memo
后的组件使用context
上下文对象的时候,一定会在context
对象发生改变时,触发重新渲染,让memo
功能失效。
特殊情况说明
假设我们传入props
是一个对象的话,这个时候该怎么办?因为我们可以知道,在React进行Props比较时,会使用Object.is
进行比较,如果是基础类型的话,如果值未发生修改,则一定返回true,但是是对象的话,在使用Object.is({}, {})
一定会返回false
,这是因为就算两个都是空对象,但是他们在堆内存的地址一定是不同的,所以一定会返回false
,那么有没有什么方法可以避免呢?
按照官网的说法,我们需要将props尽可能的最小化,也就是尽量都使用基础类型来规避这种情况,但是如果实在无法规避,必须使用对象类型或者function类型该怎么办?
这个时候就是useMemo
与useCallback
来对数据进行包裹了!
忘记给出使用示例了:
import { memo } from 'react';
const SomeComponent = memo(function SomeComponent(props) {
// ...
});
直接给官网的了,偷懒一下
useMemo
useMemo
时React中的一个勾子函数,在重复渲染的时候它能够将结果进行缓存。
注:
useMemo
都是与数据或者是回调函数打交道,尽量不要再useMemo
中直接返回一个Component。其实如果是熟悉Vue的话,你会发现,这个和Vue中的computed很像,都是对数据的缓存,然后根据deps监听是否变化在进行重新计算并更新缓存触发组件重新渲染
参数说明:
calculateValue
:calculateValue
是一个回调函数,它不会接受传入的参数,是一个纯函数,并且需要返回一个任意类型的值。React会在首次渲染时触发这个calculateValue
函数并返回执行后得到的值进行返回。在下一次渲染中,如果dependencies
未发生改变,React会返回一个相同的值,如果发生了改变,就会触发calculateValue
,更新缓存的值。dependencies
:dependencies
参数是一个数组,里面保存了会触发calculateValue
的变量,一旦其中的任意一个参数发生了改变,就会触发更新,从而更新缓存数据。
使用示例:
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
}
返回内容:
在首次渲染时,会返回calculateValue
执行后的结果。
在之后的渲染中,如果dependencies
中监听参数未发生改变,就不会有结果返回,保持原来的结果,如果发生了改变,就会返回重新执行calculateValue
函数后的结果。
使用场景:
-
协助组件跳过重新渲染
这啥意思呢?其实是这样的,如果我们有一个组件使用
memo
来声明这个组件不希望在props
或是本身state
未改变的情况重新渲染,但是在传入的props数据中,存在引用类型数据,这就会产生一个问题,在每一次渲染中,由于是引用类型数据,那么就会导致其在浅比较的时候永远不会相等,这就导致memo
失效了,memo
组件会一直重新渲染,完全达不到原有的目的了,为了解决这个问题,我们就可以使用useMemo
来解决了。我们可以将引用类型数据使用
useMemo
进行返回,只有当引用类型数据真的发生更改了,才会导致memo
组件进行更新,这样就可以有效的防止memo
组件失效的问题。具体的例子可以看官网给出的示例
-
useMemo
的嵌套使用稍微对官网示例做一下补充说明:
因为对于
FunctionComponent
来说,每一次组件重新渲染的时候,都会重新执行Function
,这就导致了每一次创建的数据集合都会重新声明,这就会出现一个问题,就是他的引用地址发生了改变了,浅比较的时候就是不同的了,这样就会使下面的useMemo
直接失效,因为useMemo
的deps
是会对这个引用对象做出响应,为了解决这个问题,就可以在对这个引用对象使用useMemo
,让其只对基础类型的改变做出响应,然后再把这个useMemo
的对象,放入到之前的useMemo
中,并且修改deps
中的响应数据,就可以解决这个问题了。 -
使用
useMemo
记忆函数这个直接看例子就好了,因为实际函数也是一个引用对象,官网例子。这里稍微提一点,可以使用
useCallback
这个语法糖,来记忆函数。
useMemo与memo的不同
React.memo()
是一个高阶组件,我们可以使用它来包装我们不想重新渲染的组件,除非其中的 props 发生变化。
useMemo()
是一个 React Hook,我们可以使用它在组件中包装函数。 我们可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算。
useCallback
useCallback
在组件的顶层调用以在重新渲染之间缓存函数定义
参数说明:
-
fn
: 需要缓存的函数。 它可以接受任何参数并返回任何值(这里与useMemo
不同的,useMemo
中calculateValue
函数是不能接受参数的)。在首次渲染的时候会返回这个函数(并不会执行)。这个fn
的改变也是取决于第二个参数dependencies
的,如果发生改变,就会更新这个函数,并且进行返回,从而触发响应。这里最重要的一点就是:React是不会执行
useCallback
中传入的fn
的,他仅仅只会去更新传递的参数,其实就相当于执行了一次bind
,改变一下传入的arguments
,然后再进行返回。 -
dependencies
:这个参数和useMemo的第二个参数相同,就不复述了,不过这里的dependencies
基本取决于影响函数结果的传入参数。
返回值:
首次渲染,返回传入的函数
后续渲染,如果dependencies
列表中影响参数未发生改变,则返回缓存中的函数,如果改变,更新后在返回
使用:
-
协助组件跳过重新渲染
这个和
useMemo
作用相同,如果是一个memo
组件,在本身渲染时间很慢的情况下,优化时,可以将memo
组件接收的props(假设这个props是一个函数),那么就可以把函数使用useCallback
进行包裹,避免memo
组件失效。毕竟Function
也是一个引用对象(根据原型链)。 -
对需要记录数据的回调更新状态
比如我们需要记录每一步状态的时候,比如在对用户动作进行采集的时候,我们就可以使用
useCallback
来对用户采集内容进行包裹,因为我们可以知道执行记录动作的函数是不会改变的,所以我们不能让其影响到组件渲染,所以需要对其进行包裹。官网给出的例子就可以很好地展示出这一点,所以我们可以再做用户动作采集的时候,使用
useCallback
来减少非必要渲染。 -
优化自定义Hook
这个还没怎么研究,毕竟初学者,所以等之后来更新
useCallback
和useMemo
两个都是对页面渲染的控制,useCallback
就像是useMemo
的语法糖,专门用来处理函数的。
总结
最近开始上班了,因为从Vue转到React,中间踩了不少的坑,十分感谢文哥的review,教会了我很多React中的使用技巧,以及特别强调了React中数据导致的问题等等的解决,加油加油,努力成为一个优秀的前端开发工程师。