前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >useMemo & useCallback 指北

useMemo & useCallback 指北

原创
作者头像
暂七师黑管手
修改2019-09-02 11:52:57
2.4K0
修改2019-09-02 11:52:57
举报
文章被收录于专栏:进击的全栈进击的全栈

最近用hooks有点上头。

很多时候我们在学习新东西之后总是会很兴奋地去做各种尝试。在React hooks正式面世之后,团队也在很多业务中开始尝试使用这种新语法。除却提及最为广泛的useStateuseEffect,也开始尝试用useMemouseCallback来重构一些组件。


认识功能

我们先来看看这两个hooks的基本形式。

代码语言:txt
复制
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

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

两个hooks的形式差不多,它们都接受两个参数:一个函数和一个可选的数组。他们的功能是,根据传入的依赖,来记录一个值或者函数。即,根据第二个参数传入的依赖数组,来判断记录的值或函数是否需要更新。其中,useMemo的第一个参数是一个返回它所记录值的函数;useCallback的第一个参数则是它所记录的方法本身。在官方给出的文档中,也明确表示:

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

了解源码

hooks的源码其实都不是很复杂的亚子。

代码语言:txt
复制
/**
 * @link https://github.com/facebook/react/blob/16.8.6/packages/react/src/ReactHooks.js
 */

export function useCallback(
  callback: () => mixed,
  inputs: Array<mixed> | void | null,
) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useCallback(callback, inputs);
}

export function useMemo(
  create: () => mixed,
  inputs: Array<mixed> | void | null,
) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMemo(create, inputs);
}
代码语言:txt
复制
/**
 * @link https://github.com/facebook/react/blob/16.8.6/packages/react-reconciler/src/ReactFiberHooks.js
 */

function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

function mountMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    // Assume these are defined. If they're not, areHookInputsEqual will warn.
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

通过areHookInputsEqual方法来对前后两次传入的deps数组进行比较,如果没有变化则返回上一次保存在memoizedState中的值或方法。

尝试使用

顺着useMemo&useCallback的设计思路,就可以着手优化代码了。主要步骤如下:

  1. 将函数式组件中的匿名函数提取出来,在函数式组件前部声明。将一些直接赋给子组件的magic number提取出来,在前部声明为常量。
  2. 检查组件前部声明的值和函数,对于不依赖组件内部数据的,直接提取到组件外部。
  3. 依赖组件内部数据的值和函数,使用useMemouseCallback进行封装。

真实情况

和理想差别很大的是,优化的效果其实并没有什么明显的提升,甚至还让代码变得有些难以理解。

事后的反思让我再次审视了这两个hooks的定义,才发现我搞错了一个很重要的概念。我的出发点是,减少组件中各种值和方法因无关属性更新产生的重复声明。

对于不依赖组件内部数据的,将它们提取到组件外部定义为常量,能够保证它们不再受到组件更新的影响:这一部分是没问题的。

问题在于useCallback并没有阻止它记录的函数被重复声明,事实上我们很容易忽视这个事实。实际上每次组件更新时它还是会重新声明一个新的函数;只是当依赖没有变化时,新声明的函数不会取代旧的函数,即只是避免了函数引用的更新。

同样,useMemo也没有减少变量的声明,甚至相比于之前,虽然减少了一些不必要的计算,但是却带来了新的函数声明操作。

不幸中的万幸

虽然没有达到预期的效果,但是useMemouseCallback并不是没什么卵用的。联想到上一节的介绍,它们能够保证在依赖不变的情况下,所记录的值和方法的引用不变。

引用不变,这个熟悉的名词,很容易让人联想到PureComponent这套东西。实际上,我觉得这才是useMemouseCallback发挥作用的催化剂。

假如你的子组件使用了PureComponent或者React.memo,那么你可以考虑使用useMemouseCallback封装提供给他们的props,这样就能够充分利用这些组件的浅比较能力。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 认识功能
  • 了解源码
  • 尝试使用
  • 真实情况
  • 不幸中的万幸
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档