useMemo - React源码解析(四)

2022-06-06  本文已影响0人  小豆soybean

原文链接:https://www.jianshu.com/p/1df8300333f7

本文主要讲React Hook之一的useMemo。
useMemo是一个非常有用的Hook,其实现非常简单的。

Mount阶段
Mount阶段(也是初始化阶段,实例第一次执行到该钩子的过程),useMemo完成以下工作:

创建一个新的Hook实例;
计算传入useMemo回调函数的值;
将计算结果以及结果的依赖记录到Hook实例对象的memoizedState属性。
返回回调函数的值。

const HooksDispatcherOnMount: Dispatcher = {
  // ...
  useMemo: mountMemo,
 // ...
};

function mountMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  // 创建一个新的Hook实例
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  // 计算传入`useMemo`回调函数的值
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

Update阶段
在Update阶段与Mount阶段的处理逻辑是不同的。

获取Mount阶段创建的Hook实例
比较两次依赖的值是否相同
如果两次依赖的值相同,直接返回上一次的计算的结果
如果两次依赖的值不相同,则再次进行计算,并记录结果,返回新的结果。

const HooksDispatcherOnUpdate: Dispatcher = {
  // ...
  useMemo: updateMemo,
 // ...
};

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  // 获取Mount阶段创建的Hook实例
  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;
}

Rerender阶段
Rerender阶段中的逻辑与Update阶段一样。这里略过不讲。

const HooksDispatcherOnRerender: Dispatcher = {
  // ...
  useMemo: updateMemo,
  // ...
};
areHookInputsEqual函数
areHookInputsEqual函数的实现原理很简单,使用is函数,依次比较两个数组中的值。

function areHookInputsEqual(
  nextDeps: Array<mixed>,
  prevDeps: Array<mixed> | null,
) {
  if (__DEV__) {
    if (ignorePreviousDependencies) {
      // Only true when this component is being hot reloaded.
      return false;
    }
  }

  if (prevDeps === null) {
    if (__DEV__) {
      console.error(
        '%s received a final argument during this render, but not during ' +
          'the previous render. Even though the final argument is optional, ' +
          'its type cannot change between renders.',
        currentHookNameInDev,
      );
    }
    return false;
  }

  if (__DEV__) {
    // Don't bother comparing lengths in prod because these arrays should be
    // passed inline.
    if (nextDeps.length !== prevDeps.length) {
      console.error(
        'The final argument passed to %s changed size between renders. The ' +
          'order and size of this array must remain constant.\n\n' +
          'Previous: %s\n' +
          'Incoming: %s',
        currentHookNameInDev,
        `[${prevDeps.join(', ')}]`,
        `[${nextDeps.join(', ')}]`,
      );
    }
  }
  // 依次比较前后两个依赖中的值
  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    if (is(nextDeps[i], prevDeps[i])) {
      continue;
    }
    return false;
  }
  return true;
}

React中对is的实现在shared包中:在支持Object.is的情况下直接调用Object.is函数; 不支持的情况下,是自实现的Object.is逻辑。

function is(x: any, y: any) {
  return (
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
  );
}

const objectIs: (x: any, y: any) => boolean =
  typeof Object.is === 'function' ? Object.is : is;

export default objectIs;
上一篇 下一篇

猜你喜欢

热点阅读