Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useMemo和useCallback源码浅析 #49

Closed
zhangyu1818 opened this issue Mar 9, 2021 · 2 comments
Closed

useMemo和useCallback源码浅析 #49

zhangyu1818 opened this issue Mar 9, 2021 · 2 comments
Labels

Comments

@zhangyu1818
Copy link
Owner

简述

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来存函数不是更妙吗?

@mackxu
Copy link

mackxu commented Oct 19, 2021

从源码看,如果useCallback不存在依赖项useCallback(fn, [])useCallback(fn)等价吧?

@zhangyu1818
Copy link
Owner Author

@mackxu 并不会等价哦
依赖传入[]的时候,会执行areHookInputsEqual来比较是否相同,相同就返回缓存的函数。
依赖不传入时和没有依赖的useEffect表现相同,每次更新都会执行,因此不会进入判断,每次都会返回最新值。
areHookInputsEqual这个方法会比较数组里的值是否相同。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants