useCallback useMomo能够达到性能优化效果源码解析（version 18.0）
Published: 2023-02-21 09:52:52

###question
为什么使用useCallback 和useMemo返回的memoized值可以起到性能优化的作用。
###源码分析

 

- 我们在packages\react\src\ReactHooks.js可以找到对应的hook
```typescript
export function useCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useCallback(callback, deps);
}

export function useMemo<T>(
  create: () => T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMemo(create, deps);
}
```


 

- 在这里我们可以看到其useCallback，useMemo是调用dispatcher对应的方法,
而dispatcher是由resolveDispatcher()生成。而resolveDispatcher()中的dispatcher 是由ReactCurrentDispatcher.current生成。因此我们需要查看ReactCurrentDispatcher.current的数据是在哪里挂载的。
```typescript
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  if (__DEV__) {
    if (dispatcher === null) {
      console.error(
        'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
          ' one of the following reasons:\n' +
          '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
          '2. You might be breaking the Rules of Hooks\n' +
          '3. You might have more than one copy of React in the same app\n' +
          'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
      );
    }
  }
//如果在渲染阶段之外访问，将导致空访问错误。我们
  //故意不抛出我们自己的错误，因为这是在热路径中。
  //也有助于确保这是内联的。
  return ((dispatcher: any): Dispatcher);
}

```


 

- 在packages\react-reconciler\src\ReactFiberHooks.js中的renderWithHooks对ReactCurrentDispatcher.current进行数据挂载这里只截取对应代码
```typescript
export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber = workInProgress;

 
  //TODO 如果挂载期间根本没有使用挂钩，则发出警告，然后在更新期间使用一些挂钩。
  //目前我们将更新渲染识别为一个装载，因为 memoizedState === null。
//这很棘手，因为它对某些类型的组件有效（例如 React.lazy）

  //使用 memoizedState 来区分挂载/更新只有在至少使用一个有状态钩子的情况下才有效。
  //非状态挂钩（例如上下文）不会添加到 memoizedState，
  //所以 memoizedState 在更新和挂载期间将为 null。

    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  }
}
```

 

- 因此当dispatcher.useCallback和dispatcher.useMemo实际调用的是会根据是否是初次渲染调用对应的HooksDispatcherOnMount和HooksDispatcherOnUpdate
```typescript
const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useInsertionEffect: mountInsertionEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useSyncExternalStore: mountSyncExternalStore,
  useId: mountId,
};
const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useInsertionEffect: updateInsertionEffect,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useSyncExternalStore: updateSyncExternalStore,
  useId: updateId,
};
```

-  HooksDispatcherOnMount和HooksDispatcherOnUpdate调用对应的方法,这里可以看到useCallback和useMemo会都会被hook.memoizedState缓存，区别就一个缓存的是函数，一个缓存的是值。对 hook.memoizedState查看我们可以了解到mountWorkInProgressHook和updateWorkInProgressHook生成，
```typescript

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;
  if (shouldDoubleInvokeUserFnsInHooksDEV) {
    nextCreate();
  }
  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];
      }
    }
  }
  if (shouldDoubleInvokeUserFnsInHooksDEV) {
    nextCreate();
  }
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

```


- mountWorkInProgressHook 
 workInProgressHook = workInProgressHook.next = hook 根据这段代码我们可以了解到一个函数组件的所有hook函数会形成一个hooks链表，而workInProgressHook 指针会指向最后一个节点。这个hooks链表是存放在currentlyRenderingFiber.memoizedState 
```typescript

function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
   //这是列表中的第一个钩子
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
   //追加到列表的末尾
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

```
- updateWorkInProgressHook 
workInProgress 树中的 fiber 节点的下一个 hook 存在，链表直接指向节点，否则就从对应的 current 的 fiber 节点克隆过来，然后把这些 hook 构建出新的链表放到 currentlyRenderingFiber.memoizedState 上，方便下次更新时使用；
```typescript
function updateWorkInProgressHook(): Hook {
//此函数用于更新和由 a 触发的重新渲染
  //渲染阶段更新。它假设有一个当前的钩子我们可以
  //克隆，或者我们可以从之前的渲染过程中进行的工作钩子
  //用作基础。当我们到达基本列表的末尾时，我们必须切换到
  //用于挂载的调度程序。
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
   //已经有一个正在进行的工作。重复使用它。
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
  } else {
    //从当前钩子克隆。

    if (nextCurrentHook === null) {
      const currentFiber = currentlyRenderingFiber.alternate;
      if (currentFiber === null) {
        //这是初始渲染。当组件到达此分支
        //挂起，恢复，然后呈现一个额外的钩子。
        const newHook: Hook = {
          memoizedState: null,

          baseState: null,
          baseQueue: null,
          queue: null,

          next: null,
        };
        nextCurrentHook = newHook;
      } else {
        // This is an update. We should always have a current hook.
        throw new Error('Rendered more hooks than during the previous render.');
      }
    }

    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,

      next: null,
    };

    if (workInProgressHook === null) {
     //这是列表中的第一个钩子。
    // 若这是链表的第一个hook节点，则使用 currentlyRenderingFiber.memoizedState 指针指向到该hook
    // currentlyRenderingFiber 是在 renderWithHooks() 中赋值的，是当前函数组件对应的fiber节点
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
   //附加到列表的末尾。
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}
```
###总结
之所以useCallback和useMemo可以达到性能优化的效果，是因为我们在初始节点已经把所有的 hooks 都挂载在链表中了，在更新阶段，所有的 hooks 都会进入到 update 阶段，比如 useState()内部会执行 updateState()，useEffect()内部会执行 updateEffect()等。二这些 hooks 的 update 阶段执行的函数里，都会执行函数 updateWorkInProgressHook()。

updateWorkInProgressHook()函数的作用，就是从 hooks 的链表中获取到当前位置，上次渲染后和本次将要渲染的两个 hook 节点：

currentHook: current 树中的那个 hook；即当前正在使用的那个 hook；

workInProgressHook: workInProgress 树中的那个 hook，即将要执行的 hook；
当通过对比两个hook，当依赖项没有发生改变时，workInProgressHook链表的指针会直接指向之前的fiber节点的地址。而不会重新创建。因此达到性能优化的效果


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ugrfjt77yjfj4agepy7h.png)