返回

useMemo 与 useCallback 解析:React 源码系列揭秘优化之道

前端




前言:性能优化的必要性

React 是一个受欢迎的前端库,以其高效的虚拟 DOM Diffing 算法而著称,但是随着应用变得复杂,性能优化仍然是开发者需要关注的问题。其中,不必要的数据重新渲染是导致性能下降的主要原因之一。

当组件重新渲染时,React 会重新计算所有子组件的 props 和 state,然后进行 Diffing 算法来决定哪些组件需要实际更新。如果组件树很大,或者组件内部有复杂的数据结构,那么重新渲染的成本就会非常高。

认识 useMemo 和 useCallback

useMemo 和 useCallback 都是 React Hook,用于缓存数据和函数,可以帮助我们避免不必要的重新渲染。

useMemo:缓存数据

useMemo 函数可以将一个计算结果缓存起来,并在下次组件更新时检查依赖项是否发生变化。如果依赖项没有变化,那么就会返回之前缓存的结果,避免重新计算。

语法如下:

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

在上面的代码中,我们使用 useMemo 将 computeExpensiveValue 的结果缓存起来,并将其存储在 memoizedValue 中。当组件重新渲染时,useMemo 会检查 props 是否发生变化,如果没有变化,那么就会返回之前缓存的结果,避免重新计算 computeExpensiveValue。

useCallback:缓存函数

useCallback 函数可以将一个函数缓存起来,并在下次组件更新时检查依赖项是否发生变化。如果依赖项没有变化,那么就会返回之前缓存的函数,避免重新创建函数。

语法如下:

const memoizedCallback = useCallback(() => {
  // 这里可以做一些事情
}, [props]);

在上面的代码中,我们使用 useCallback 将匿名函数缓存起来,并将其存储在 memoizedCallback 中。当组件重新渲染时,useCallback 会检查 props 是否发生变化,如果没有变化,那么就会返回之前缓存的函数,避免重新创建匿名函数。

源码解析

接下来,我们来深入分析 useMemo 和 useCallback 的源码,看看它们是如何工作的。

useMemo 源码解析

export function useMemo(factory, deps) {
  if (process.env.NODE_ENV !== 'production') {
    if (!isValidElementType(factory)) {
      // ...
    }
  }

  const ref = useRef();

  // ...

  if (diff(inputs, nextDeps)) {
    cleanup(value);

    value = factory();

    useMutableSource(value, source);
  }

  return value;
}

从源码中可以看出,useMemo 内部使用了一个 useRef Hook 来存储缓存的值。当组件重新渲染时,useMemo 会比较依赖项是否发生变化,如果依赖项发生变化,那么就会重新计算 factory 的值,并将其存储在 useRef 中。

useCallback 源码解析

export function useCallback(callback, deps) {
  if (process.env.NODE_ENV !== 'production') {
    if (callback !== null && typeof callback !== 'function') {
      // ...
    }
  }

  const ref = useRef(callback);

  // ...

  if (process.env.NODE_ENV !== 'production') {
    checkInvalidReturnType(functionName, callback);
  }

  return ref.current;
}

从源码中可以看出,useCallback 内部也使用了一个 useRef Hook 来存储缓存的函数。当组件重新渲染时,useCallback 会比较依赖项是否发生变化,如果依赖项发生变化,那么就会重新创建 callback 函数,并将其存储在 useRef 中。

正确使用 useMemo 和 useCallback

useMemo 和 useCallback 是非常有用的 Hook,但是如果使用不当,也可能会导致性能问题。以下是一些正确使用 useMemo 和 useCallback 的建议:

  • 只缓存纯函数: useMemo 和 useCallback 只能缓存纯函数,这意味着函数的返回值只取决于函数的参数,而不会产生副作用。
  • 只缓存计算代价高的函数: useMemo 和 useCallback 只应该用于缓存计算代价高的函数,如果函数的计算代价很低,那么就没有必要使用它们。
  • 只缓存必要的依赖项: useMemo 和 useCallback 的依赖项应该尽可能少,只包含那些会影响函数返回值的依赖项。如果依赖项太多,那么就会导致不必要的重新计算。

总结

useMemo 和 useCallback 是两个非常有用的 React Hook,可以帮助我们避免不必要的重新渲染,从而提升应用性能。但是,在使用它们时,我们应该遵循正确的使用建议,以避免导致性能问题。