easycodesniper

blog by chen qiyi

useMemo & useCallback

useMemo

useMemo用于缓存每次重新渲染都需要计算的结果

语法

useMemo(calculateValue,dependencies)

  • calculateValue: 缓存计算值的函数,它应该是一个没有参数的纯函数。React将在首次渲染时调用该函数,在之后的渲染中,如果依赖项(dependencies)没有发生变化,React将不会调用该函数,而是直接返回缓存的值
  • dependencies: 依赖项

返回值

  • 在初次渲染时,返回calculateValue函数的返回值
  • 在之后的渲染中,如果依赖项没有变化,则返回缓存的值;如果依赖项发生了变化,将重新调用calculateValue函数并计算出新的返回值

用法

useMemo主要的用法有两种:

  • 通过缓存数据,来跳过代价昂贵的计算
  • 跳过组件的重新渲染

跳过代价昂贵的计算

例如,我们有一个todos的大数组,需要根据tab来筛选出某些数组项。在初次渲染之后,只有当todos数组或者tab发生改变了才会重新执行过滤数组的方法。

1
2
3
4
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}

跳过组件的重新渲染

例如,ParentComp组件接受一个theme的props,用于改变组件内部样式,默认情况下,一个组件重新渲染时,React会递归重新渲染它的所有子组建。也就是,当theme重新渲染时,会导致SonComp组件的重新渲染,但其实SonComp组件并不需要重新渲染。

1
2
3
4
5
6
7
8
9
function ParentComp({ theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
return (
<div className={theme}>
<SonComp items={visibleTodos}/>
</div>
)
}

可以通过将组件用memo包裹起来,这样当它的props和上次传入的相同时,就不会触发重新渲染

1
2
3
4
import { memo } from 'react';
const SonComp = memo(function SonComp({ items }) {
// ...
})

引出usecallback

现在,SonComp组件被包裹在memo中,接收一个函数作为props。每次ParentComp组件重新渲染,都会导致handleClick函数的重新创建,即产生不同的handleClick,这就会导致SonComp组件的重新渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function ParentComp({ theme }) {

function handleClick = () => {
// ...
}

// ...
return (
<SonComp handleClick={handleClick}/>
)
}

const SonComp = memo(function SonComp({ handleClick }) {
// ...
})

我们很容易想到使用useMemo来缓存handleClick函数,如下所示:

1
2
3
4
5
const handleClick = useMeme(() => {
return () => { // 原来handleClick的逻辑

}
},[xxx])

这样看上去就很冗余了,在React中有另外一个Hook用于缓存函数。

useCallback

useCallback允许你在多次渲染中缓存函数

语法

useCallback(fn, dependencies)

  • fn: 想要缓存函数,它可以传入任何参数并且返回任何值。React将在首次渲染时返回(不是调用)该函数,在之后的渲染中,如果依赖项(dependencies)没有发生变化,React将返回相同的函数。在任何渲染中,React都不会调用该函数,而是返回该函数
  • dependencies: 依赖项

返回值

在初次渲染时,useCallback返回你已经传入的fn函数

在之后的渲染中, 如果依赖没有改变,useCallback返回上一次渲染中缓存的fn函数;否则返回这一次渲染传入的fn

用法

接着上面的例子对handleClick进行改造,使它也被缓存起来

1
2
3
const handleClick = useCallback((args) => { //和普通函数一样使用
// 直接在此处书写逻辑代码
},[xxx])