React中的useMemo和useCallback
先看看二者的基本定义
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 返回一个带有缓存(记忆)功能的值。
- 输入
- create函数(用来创建/生成要缓存的值,所以叫create函数)
- 依赖项列表(指明什么时候需要重新计算并覆盖之前缓存的值)
useMemo
仅在某个依赖项改变时才会触发重新计算。这种优化可以避免在每次渲染时都进行不必要的高开销计算。
另外就是当依赖项发生改变时,传入的函数会在渲染时执行。所以函数中应该避免那些在渲染时不该做的操作,比如那些应该在useEffect
中执行的副作用操作。
如果没有指明依赖列表,每次渲染时都会重新计算。
所以从这个角度来说,useMemo
只应该被用来作为性能优化的一种方法,而不是某种语义的保证。
将来,React可能会根据需要选择“忘记”某些记忆的值,在重新渲染的时候如论如何都触发重新计算,比如在组件不再显示时释放内存。所以我们的代码应该能够在没有useMemo
的时候一样能够正常工作,然后把useMemo
仅仅作为性能优化的一部分。
注意:依赖项列表不会作为参数传递给create函数。尽管从概念上来这就是依赖项代表的意义,函数中引用的每一个值都应该在依赖列表中出现。将来,这些依赖列表可能会由足够高级的编译器来自动生成。
我们建议使用exhaustive-deps
规则作为eslint-plugin-react-hooks
包的一部分。这样就可以在依赖性声明不正确时给出警告和修复建议。
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
- 返回一个具有缓存(记忆)功能的回调函数。
- 输入
- 内联函数(要缓存的函数)
- 依赖项列表(指明什么时候需要重新定义函数)
useCallback会返回一个具有记忆功能版本的回调函数,仅仅在依赖项中的至少一个发生变化的时候改变。当传递回调给那些使用依赖引用对等性进行性能优化来避免不必要的渲染的子组件时会非常有用(例如shouldComponentUpdate
)。
useCabllback(fn, deps)
相当于 useMemo(() => fn, deps)
。
useMemo和useCallback比较
相同点
- 输入参数相同
- 从定义来说,两个的输入是一样的,都是一个函数和依赖项列表。
- 目的相同/类似
- 从功能和目的上来说,
useMemo
和useCallback
都是在某种意义上的性能优化。要么减少不必要的计算(由于父组件中state变化),要么减少不必要的函数重新定义(父组件中state变化导致组件重新渲染,同时会触发函数定义的刷新)。 - 本身并不影响程序的任何逻辑流程。
- 从功能和目的上来说,
不同点
- (从定义及语法上看)返回值不同
useMemo
- 返回具有缓存功能的值,缓存的是函数的返回值。useCallback
- 返回具有缓存功能的回调函数,缓存的是函数本身。
- 使用场景不同
这个就很好理解了,因为有不同的返回值,所以对应的使用场景也不同。useMemo
- 从父组件传递变量到子组件,用来避免子组件内一些不必要的高开销计算。适用于高开销计算的场景,节约内存资源。useCallback
- 从父组件传递回调函数到子组件,用来避免由于父组件自身state变化而重新渲染时的不必要的回调函数的重新生成。这样当把callback传递给子组件时,就会触发子组件的重新渲染(每次都是新生成的函数props),有些情况下,会使子组件内的一些性能优化失效。