ZHANGYU.dev

October 14, 2023

useMemo和useCallback源码浅析

React2.4 min to read

简述

useMemouseCallback相对来说源码比较简单,在函数组件执行到对应的Hook时,同样会将包含该Hook信息的对象链接到Fiber节点的memoizedState属性上的Hooks链表。

useMemoHook对象的memoizedState属性上存的值为计算后的值和依赖数组 —— hook.memoizedState = [nextValue, nextDeps]

useCallbackHook 对象的memoizedState属性上存的值为回调函数和依赖数组 —— hook.memoizedState = [callback, nextDeps]

以下源码浅析React版本为17.0.1。

useMemo

在React中,Hooks在Mount时和Update时使用的是两个不同函数(useContext除外)。

Mount时

function mountMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  // 添加到Fiber节点上的Hooks链表
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  // 计算需要memo的值
  const nextValue = nextCreate();
  // hook数据对象上存的值
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

Update时

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  // 找到该useMemo对应的hook数据对象
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  // 之前存的[nextValue, nextDeps]
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    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;
}

useCallback

之前有看别人讲useCallbackuseMemo的语法糖,现在一看,虽然两个方法完全不同,但也基本完全相同了。

Mount时

function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  // 添加到Fiber节点上的Hooks链表
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  // memoizedState存的值是callback
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

Update时

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  // 找到该useMemo对应的hook数据对象
  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;
}

这俩方法的源码也太短了,这样就水了一文。

其实一直有一个疑问,如果真的要想让一个函数的地址不发生变化,用useRef来存函数不是更妙吗?