useMemo 和 useCallback 带来的性能优化
前言
随着函数式组件的产生,React Hooks
给我们带来了很多便利和新功能。先前我在《React Hooks 入门》这篇文章中简单介绍了几种常用的 Hooks
。今天,本篇文章将谈一谈 useCallback
和 useMemo
两个钩子函数。
useCallback
首先我们先看一下官方文档中对 useCallback
的说明:
Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
简单来说,它和 useEffect
的基本原理类似:有两个参数,第一个参数是 内联回调函数,第二个是 依赖项数组。它将返回该回调函数的 memoized
版本,该回调函数仅在某个依赖项改变时才会更新。
一句话总结:缓存函数,仅在依赖项改变的时候才会更新。 由此可知, useCallback
可以有利于我们判断历史和当前的内联回调函数的相等性,从而避免非必要渲染(例如 shouldComponentUpdate
)的子组件。因为我们知道,即使两个内容完全相同的函数,在 js 中判等也是 false。
下面我们举个栗子:
App.js
入口文件,作为父组件。
1 | // App.js |
RandomNum.js
为子组件,包括:点击按钮 和 随机数。
1 | // RandomNum.js |
上述代码的实验结果如下:
- 点击 Button1,只有 Button1 后面的随机数改变。
- 点击 Button2,Button1 和 Button2 后面的随机数均改变。
详细实验结果如下所示:
1、初始化结果:
2、点击 Button1 后,Button1 后面的随机数改变。
3、点击 Button2 后,Button1 和 Button2 后面的随机数都改变。
了解过 React
的渲染机制的应该都知道:当父组件更新时,子组件也会跟着重新渲染。因此,当点击 Button1
或者 Button2
,会导致 App
这个父组件更新,从而导致两个子组件也更新。但是 Button2
的点击事件通过 useCallback
封装了起来,仅仅在 count2
改变时,才会更新内联函数。而 Button1
的点击事件会在父组件更新后也跟着更新,重新触发更新后的事件。
由此可知,点击 Button1
会导致其后面的随机数改变,而 Button2
的随机数不会改变,因为 Button2
没有更新;点击 Button2
会导致两个按钮后的随机数都改变,因为两个按钮都更新了。
useMemo
首先我们先看一下官方文档中对 useMemo
的说明:
Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed.
简单来说,useMemo
的参数包括一个创建函数和依赖项。创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。
举个栗子,如下所示:
1 | // 使用 useMemo |
1 | // 不使用 useMemo |
从上面的栗子可以看出,第一段代码使用了 useMemo
,即只有当 age
属性改变时,userInfo
的值才会改变,子组件 User
才会重新渲染;第二段代码没有使用 useMemo
,是要父组件 App
重新渲染,子组件 User
也会跟着渲染。
因此,使用 useMemo
可以减少一些不必要的渲染,从而提升性能。继续想下去,那么我们可以把一些具有昂贵的逻辑运算或数据处理放到 useMemo
中,实现缓存,这样可以大大减少每次渲染的开销。
如果没有提供依赖项数组,useMemo
在每次渲染时都会计算新的值,这和 useEffect
没有提供依赖项数组是同样的处理情况。
肯定会有人问这个 useMemo
和 useEffect
有什么关系,看起来挺像的。记住,**传入 useMemo
的函数会在渲染期间执行。请不要在这个函数内部执行不应该在渲染期间内执行的操作,诸如副作用这类的操作属于 useEffect
的适用范畴,而不是 useMemo
**。
总结
useCallback
可以封装函数,以保存内联函数的不变性,即缓存函数。可以利用引用判等性和 shouldComponentUpdate
结合使用子组件避免一些不必要的渲染。
useMemo
缓存的是函数返回值,它既可以优化子组件的不必要渲染,也可以存储当前组件的一些复杂逻辑计算环节。
很感谢你能看到这里!谢谢~
参考资料: (衷心感谢各参考资料提供的帮助)