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