memo,useMemo,useCallback之间的关联

451 阅读8分钟

memo,useMemo,useCallback之间的关联

创建人内容创建时间更新时间
adsionlimemo,useMemo,useCallback之间的关联2023.04.202023.04.20

在React中,我们可以主动的进行渲染控制以减少不必要的重复渲染开销,可以通过使用memo,useMemo,useCallback这三个React提供的内容。

但是在使用memo,useMemo,useCallback之前,我们必须要保证,在我们缓存的组件不能够出现会影响缓存状态的东西,比如在待缓存的组件中使用useContext等内容,我们可以将context的内容通过props的形式传入到缓存组件中。这样做的原因是因为我们一旦在memo缓存组件中使用了useContext来获取上下文状态时,就会破化缓存性,导致其必然会受到重新渲染的问题。

这里被破化的情况在react官网有提到

memo

memo文档

memo的作用是在子组件接受的props未发生改变时跳过重新渲染的一个高阶组件,它通常会和useMemouseCallback进行配合使用。

参数说明

  1. Component: 需要被缓存的组件,memo不会修改组件内容,只会返回一个新的记忆化组件。
  2. arePropsEqual: 可选参数,用于协助判断组件前后的props是否相等,可以接受两个参数:prevPropsnewProps,如果prevProps == newProps,需要返回true,即组件渲染输出结果相同且newProps造成的行为也完全相同。(主要是为了进行更深层次的比较,因为本身的比较是浅比较,所以为了能够更好的控制渲染更新,可自定深比较)

返回值

memo 返回一个新的 React 组件。它的行为与提供给 memo 的组件相同,除了 React 不会总是在其父级被重新渲染时重新渲染它,除非它的props发生了变化。

使用memo时需要注意的点:

  1. 当使用memo后返回的组件本身具有的state改变后,组件会触发重新渲染。
  2. 如果memo后的组件使用context上下文对象的时候,一定会在context对象发生改变时,触发重新渲染,让memo功能失效。

特殊情况说明

假设我们传入props是一个对象的话,这个时候该怎么办?因为我们可以知道,在React进行Props比较时,会使用Object.is进行比较,如果是基础类型的话,如果值未发生修改,则一定返回true,但是是对象的话,在使用Object.is({}, {})一定会返回false,这是因为就算两个都是空对象,但是他们在堆内存的地址一定是不同的,所以一定会返回false,那么有没有什么方法可以避免呢?

按照官网的说法,我们需要将props尽可能的最小化,也就是尽量都使用基础类型来规避这种情况,但是如果实在无法规避,必须使用对象类型或者function类型该怎么办?

这个时候就是useMemouseCallback来对数据进行包裹了!

忘记给出使用示例了:

import { memo } from 'react';
​
const SomeComponent = memo(function SomeComponent(props) {
  // ...
});

直接给官网的了,偷懒一下

useMemo

useMemo时React中的一个勾子函数,在重复渲染的时候它能够将结果进行缓存。

注:useMemo都是与数据或者是回调函数打交道,尽量不要再useMemo中直接返回一个Component。

其实如果是熟悉Vue的话,你会发现,这个和Vue中的computed很像,都是对数据的缓存,然后根据deps监听是否变化在进行重新计算并更新缓存触发组件重新渲染

参数说明:

  1. calculateValue: calculateValue是一个回调函数,它不会接受传入的参数,是一个纯函数,并且需要返回一个任意类型的值。React会在首次渲染时触发这个calculateValue函数并返回执行后得到的值进行返回。在下一次渲染中,如果dependencies未发生改变,React会返回一个相同的值,如果发生了改变,就会触发calculateValue,更新缓存的值。
  2. dependencies:dependencies参数是一个数组,里面保存了会触发calculateValue的变量,一旦其中的任意一个参数发生了改变,就会触发更新,从而更新缓存数据。

使用示例:

import { useMemo } from 'react';
​
function TodoList({ todos, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
}

返回内容:

在首次渲染时,会返回calculateValue执行后的结果。

在之后的渲染中,如果dependencies中监听参数未发生改变,就不会有结果返回,保持原来的结果,如果发生了改变,就会返回重新执行calculateValue函数后的结果。

使用场景:

  1. 协助组件跳过重新渲染

    这啥意思呢?其实是这样的,如果我们有一个组件使用memo来声明这个组件不希望在props或是本身state未改变的情况重新渲染,但是在传入的props数据中,存在引用类型数据,这就会产生一个问题,在每一次渲染中,由于是引用类型数据,那么就会导致其在浅比较的时候永远不会相等,这就导致memo失效了,memo组件会一直重新渲染,完全达不到原有的目的了,为了解决这个问题,我们就可以使用useMemo来解决了。

    我们可以将引用类型数据使用useMemo进行返回,只有当引用类型数据真的发生更改了,才会导致memo组件进行更新,这样就可以有效的防止memo组件失效的问题。

    具体的例子可以看官网给出的示例

  2. useMemo的嵌套使用

    官网示例

    稍微对官网示例做一下补充说明:

    因为对于FunctionComponent来说,每一次组件重新渲染的时候,都会重新执行Function,这就导致了每一次创建的数据集合都会重新声明,这就会出现一个问题,就是他的引用地址发生了改变了,浅比较的时候就是不同的了,这样就会使下面的useMemo直接失效,因为useMemodeps是会对这个引用对象做出响应,为了解决这个问题,就可以在对这个引用对象使用useMemo,让其只对基础类型的改变做出响应,然后再把这个useMemo的对象,放入到之前的useMemo中,并且修改deps中的响应数据,就可以解决这个问题了。

  3. 使用useMemo记忆函数

    这个直接看例子就好了,因为实际函数也是一个引用对象,官网例子。这里稍微提一点,可以使用useCallback这个语法糖,来记忆函数。

useMemo与memo的不同

  1. React.memo() 是一个高阶组件,我们可以使用它来包装我们不想重新渲染的组件,除非其中的 props 发生变化
  1. useMemo() 是一个 React Hook,我们可以使用它在组件中包装函数。 我们可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算

useCallback

useCallback在组件的顶层调用以在重新渲染之间缓存函数定义

参数说明:

  1. fn: 需要缓存的函数。 它可以接受任何参数并返回任何值(这里与useMemo不同的,useMemocalculateValue函数是不能接受参数的)。在首次渲染的时候会返回这个函数(并不会执行)。这个fn的改变也是取决于第二个参数dependencies的,如果发生改变,就会更新这个函数,并且进行返回,从而触发响应。

    这里最重要的一点就是:React是不会执行useCallback中传入的fn的,他仅仅只会去更新传递的参数,其实就相当于执行了一次bind,改变一下传入的arguments,然后再进行返回。

  2. dependencies:这个参数和useMemo的第二个参数相同,就不复述了,不过这里的dependencies基本取决于影响函数结果的传入参数。

返回值:

首次渲染,返回传入的函数

后续渲染,如果dependencies列表中影响参数未发生改变,则返回缓存中的函数,如果改变,更新后在返回

使用:

  1. 协助组件跳过重新渲染

    这个和useMemo作用相同,如果是一个memo组件,在本身渲染时间很慢的情况下,优化时,可以将memo组件接收的props(假设这个props是一个函数),那么就可以把函数使用useCallback进行包裹,避免memo组件失效。毕竟Function也是一个引用对象(根据原型链)。

  2. 对需要记录数据的回调更新状态

    比如我们需要记录每一步状态的时候,比如在对用户动作进行采集的时候,我们就可以使用useCallback来对用户采集内容进行包裹,因为我们可以知道执行记录动作的函数是不会改变的,所以我们不能让其影响到组件渲染,所以需要对其进行包裹。

    官网给出的例子就可以很好地展示出这一点,所以我们可以再做用户动作采集的时候,使用useCallback来减少非必要渲染。

  3. 优化自定义Hook

    这个还没怎么研究,毕竟初学者,所以等之后来更新

useCallbackuseMemo两个都是对页面渲染的控制,useCallback就像是useMemo的语法糖,专门用来处理函数的。

总结

最近开始上班了,因为从Vue转到React,中间踩了不少的坑,十分感谢文哥的review,教会了我很多React中的使用技巧,以及特别强调了React中数据导致的问题等等的解决,加油加油,努力成为一个优秀的前端开发工程师。