useMemo与useCallback


useMemo与useCallback

useMemouseCallback都可缓存函数的引用或值,从更细的角度来说useMemo则返回一个缓存的值,useCallback是返回一个缓存函数的引用。

useMemo

useMemoTS定义可以看出,范型TuseMemo中是一个返回的值类型。

type DependencyList = ReadonlyArray;

function useMemo(factory: () => T, deps: DependencyList | undefined): T;

下面是useMemo的简单示例,在ab的变量值不变的情况下,memoizedValue的值不变,在此时useMemo函数的第一个参数也就是computeExpensiveValue函数不会被执行,从而达到节省计算量的目的。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

把创建函数factory: () => T和依赖项数组deps: DependencyList | undefined作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算memoized 值,这种优化有助于避免在每次渲染时都进行高开销的计算,例如上文的computeExpensiveValue是需要一个大量计算的函数时,useMemo有助于减少性能开销,以防止Js太多次长时间运行计算导致页面无响应。
此外,传入useMemo的函数会在渲染期间执行,所以不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect的适用范畴,而不是useMemo。如果没有提供依赖项数组,useMemo在每次渲染时都会计算新的值。
eslinteslint-plugin-react-hooks中的exhaustive-deps规则可以在添加错误依赖时发出警告并给出修复建议。
相比较于useEffect看起来和VueWatch很像,但是思想方面是不同的,Vue是监听值的变化而React是用以处理副作用。在useMemo方面就和Vuecomputed非常类似了,同样都属于缓存依赖项的计算结果,当然在实现上是完全不同的。

useCallback

useCallbackTS定义可以看出,范型TuseCallback中是一个返回的函数类型。

type DependencyList = ReadonlyArray;

function useCallback any>(callback: T, deps: DependencyList): T;

下面是useCallback的简单示例,在ab的变量值不变的情况下,memoizedCallback的函数引用不变,在此时useCallback函数的第一个参数不会被重新定义,即引用的依旧是原函数,从而达到性能优化的目的。

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

把内联回调函数callback: T及依赖项数组deps: DependencyList作为参数传入 useCallback,它将返回该回调函数的memoized版本,该回调函数仅在某个依赖项改变时才会更新,将回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。此外,useCallback(fn, deps)相当于useMemo(() => fn, deps),由此useCallback可以看作useMemo的语法糖。
eslinteslint-plugin-react-hooks中的exhaustive-deps规则可以在添加错误依赖时发出警告并给出修复建议。
useCallback的应用方面,在这里引用一下 @松松 给出的例子,一般Js上创建一个函数需要的时间并不至于要缓存的程度,那为什么要专门给缓存函数的创建做一个语法糖呢,这就跟React.memo有关系了。React.memo的默认第二参数是浅对比shallow compare上次渲染的props和这次渲染的props,如果你的组件的props中包含一个回调函数,并且这个函数是在父组件渲染的过程中创建的(见下例),那么每次父组件(下例中的)渲染时,React是认为你的子组件(下例中的