一文带你深入 React Hooks 原理与源码

3,103 阅读13分钟

前言

阅读本文前建议先了解 React 渲染相关流程与基本原理,以下是之前针对源码写的粗略文章:

本文对上述源码原理部分不做太多详细说明。

附:我的 Github 地址

renderWithHooks 函数

在这篇文章 深入 React 源码 render 阶段的 beginWork 流程 提到,当 React 渲染时,判断当前为函数组件,会执行 updateFunctionComponent

// 函数组件
case FunctionComponent: {
  return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes)
}

updateFunctionComponent 会调用 renderWithHooks 方法,这个方法就是 Hooks 执行的入口了

renderWithHooks 方法核心就是确定 ReactCurrentDispatcher,执行函数组件函数

function renderWithHooks(
  current, // 当前函数组件对应的初始化 fiber
  workInProgress, // 当前正在工作的 fiber 对象
  Component, // 函数组件本身
  props, // 函数组件第一个参数 props
  secondArg, // 函数组件其他参数
  nextRenderLanes // 下次渲染优先级
) {
  renderLanes = nextRenderLanes
  // 正在处理的函数组件对应 Fiber
  currentlyRenderingFiber = workInProgress

  // 置空
  workInProgress.memoizedState = null // memoizedState 用于保存 hooks 链表信息
  workInProgress.updateQueue = null // 存放副作用存储的链表
  workInProgress.lanes = NoLanes

  // 首次挂载和更新阶段分配不同的调度器
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate
  // 执行函数组件
  let children = Component(props, secondArg)

  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    // 防止太多次数的重新渲染
  }
  // 还原调度器
  ReactCurrentDispatcher.current = ContextOnlyDispatcher

  // 重置全局变量
  renderLanes = NoLanes
  currentlyRenderingFiber = null

  currentHook = null
  workInProgressHook = null

  return children
}

通过上面代码可以总结, renderWithHooks 方法主要工作是:

  1. workInProgress 赋值给 currentlyRenderingFiber,置空 workInProgress 树的 memoizedStateupdateQueue
  2. 判断当前是挂载还是更新阶段,挂载则赋予 ReactCurrentDispatcher.current值为 HooksDispatcherOnMount,更新阶段则赋予值为 HooksDispatcherOnUpdate
  3. 执行函数组件 Component(props, secondArg)
  4. 重置全局变量为 null。如 currentHookworkInProgressHook,以及ReactCurrentDispatcher.current 重置为 ContextOnlyDispatcher,防止在错误时机使用 Hook
// 首次渲染挂载
const HooksDispatcherOnMount = {
  readContext,
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useOpaqueIdentifier: mountOpaqueIdentifier,
}

// 更新
const HooksDispatcherOnUpdate = {
  readContext,
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useOpaqueIdentifier: updateOpaqueIdentifier,
}

也就是说,通过给 ReactCurrentDispatcher 赋值不同的变量来调用 hooks 不同阶段的处理函数。比如使用 useState 的时候,首次挂载实际调用的是HooksDispatcherOnMount.useState,即 mountState 方法;更新时调用的是HooksDispatcherOnUpdate.useState,即updateState 方法。

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current
  return dispatcher
}

function useState(initialState) {
  const dispatcher = resolveDispatcher()
  return dispatcher.useState(initialState)
}

ReactCurrentDispatcher 在函数组件使用时有HooksDispatcherOnMountHooksDispatcherOnUpdate两种值,当执行完函数组件,就会被重置为 ContextOnlyDispatcher。如果开发时调用了这个形态下的 hooks 会抛出错误,用于防止开发者在函数组件外部调用 hooks

export const ContextOnlyDispatcher = {
  readContext,
  useCallback: throwInvalidHookError,
  useContext: throwInvalidHookError,
  useEffect: throwInvalidHookError,
  useImperativeHandle: throwInvalidHookError,
  useLayoutEffect: throwInvalidHookError,
  useMemo: throwInvalidHookError,
  useReducer: throwInvalidHookError,
  useRef: throwInvalidHookError,
  useState: throwInvalidHookError,
  useDebugValue: throwInvalidHookError,
  useDeferredValue: throwInvalidHookError,
  useTransition: throwInvalidHookError,
  useMutableSource: throwInvalidHookError,
  useOpaqueIdentifier: throwInvalidHookError,
}

function throwInvalidHookError() {
  invariant(
    false,
    '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.'
  )
}

Hooks 原理

useState

mountState

讲完公共的入口函数,接下来来单独看下每个 Hooks 的源码。上文说到 useState,先来看看挂载时调用的 mountState

function mountState(initialState) {
  // 创建 hook 对象,添加到 workInProgressHook 链表
  const hook = mountWorkInProgressHook()
  // useState 第一个参数为函数,则执行函数进行初始化赋值
  if (typeof initialState === 'function') {
    initialState = initialState()
  }
  hook.memoizedState = hook.baseState = initialState
  const queue = (hook.queue = {
    pending: null,
    dispatch: null, // 负责更新 state 的函数
    lastRenderedReducer: basicStateReducer, // 自带 reducer 默认值
    lastRenderedState: initialState,
  })
  // 负责更新 state
  const dispatch = (queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber, queue))
  return [hook.memoizedState, dispatch]
}

这里可以知道,hook.memoizedState 的值就是我们外部使用时 state 的值

再来看下 mountWorkInProgressHook 函数,作用是新建一个 hook 对象并添加到 hooks 链表,返回 workInProgressHookworkInProgressHook 可以按 hook 调用顺序指向最新的 hook

function mountWorkInProgressHook() {
  // 创建一个 hook 对象
  const hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null,
  }

  // 第一个 Hook
  if (workInProgressHook === null) {
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook
  } else {
    // 链表形式串联
    workInProgressHook = workInProgressHook.next = hook
  }
  return workInProgressHook
}

dispatchAction 函数的作用是触发更新

function dispatchAction(fiber, queue, action) {
  const eventTime = requestEventTime()
  const lane = requestUpdateLane(fiber)
  // 创建一个 update 对象,记录此次更新的信息
  const update = {
    lane,
    action,
    eagerReducer: null,
    eagerState: null,
    next: null,
  }
  // 待更新队列
  const pending = queue.pending
  // 第一次更新
  if (pending === null) {
    update.next = update // 链表为空,则指向自己本身
  } else {
    update.next = pending.next
    pending.next = update
  }
  queue.pending = update // 指向最新的 update

  const alternate = fiber.alternate
  // 判断当前 fiber 是否处在渲染阶段
  if (fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber)) {
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true
  } else {
    // 当前 fiber 没有处在渲染阶段
    if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
      const lastRenderedReducer = queue.lastRenderedReducer
      if (lastRenderedReducer !== null) {
        try {
          const currentState = queue.lastRenderedState
          // 调用 lastRenderedReducer 获取最新的 state
          const eagerState = lastRenderedReducer(currentState, action)

          update.eagerReducer = lastRenderedReducer
          update.eagerState = eagerState
          // is 方法原理是 Object.is 进行浅比较
          if (is(eagerState, currentState)) {
            return
          }
        } catch (error) {
        } finally {
        }
      }
    }
    // 调度更新
    scheduleUpdateOnFiber(fiber, lane, eventTime)
  }
}

触发更新都会调用到 scheduleUpdateOnFiber 方法,它是整个更新任务的开始。这个方法之前文章已经过了下源码,具体可见:深入 React 源码 render 阶段的 beginWork 流程 - scheduleUpdateOnFiber

总体做的事情:

  1. 创建一个 update 对象记录更新信息,加入到待更新的 pending 队列
  2. 判断当前 fiber 是否处于渲染阶段,是则不需要更新当前函数组件;不是则需要执行更新
  3. 执行更新的操作:获取最新 state,与上一次的 state 做浅比较,如果相等则不需要更新;不相等则往下执行 scheduleUpdateOnFiber 进行调度更新。

Hooks 数据结构,存储 hooks 状态和更新相关的信息:

const hook = {
  memoizedState: null, // 存储了特定 hook 的缓存状态。对于不同的 hook 其值会有所不同
  baseState: null, // 存储了 hook 的初始状态
  baseQueue: null, // 初始队列
  queue: null, // 需要更新的队列
  next: null, // 下一个 hook
}

updateState

讲完了 useState 的挂载,接下来来看看更新的函数 updateState

function updateState(initialState) {
  return updateReducer(basicStateReducer, initialState)
}

function basicStateReducer(state, action) {
  return typeof action === 'function' ? action(state) : action
}

const HooksDispatcherOnUpdate = {
  useReducer: updateReducer,
  useState: updateState,
}

我们可以看到 useState 的更新时调用的是 updateReducer 方法,底层复用了 useReducer 的更新方法,所以可以说 useState 其实就是有默认 reducer 参数(basicStateReducer) 的 useReducer

  • markSkippedUpdateLanes:React 的调度和渲染过程中,可能会有一些低优先级的更新被跳过,以便更快地处理高优先级的更新。而该函数的主要目的就是将这些被跳过的更新标记起来,以便在后续的渲染过程中重新处理它们

updateReducer 主要工作是处理组件的 state 更新,大致如下:

  1. 合并并处理所有的更新
  2. 遍历队列的 update 对象,使用 action 和 reducer 计算出最新的状态,并返回最新的 state 和更新 state 的函数
function updateReducer(reducer, initialArg, init) {
  // 获取当前 fiber 对象的 hook 信息,即 workInProgressHook
  const hook = updateWorkInProgressHook()
  const queue = hook.queue
  queue.lastRenderedReducer = reducer
  const current = currentHook
  const baseQueue = current.baseQueue
  const pendingQueue = queue.pending

  if (pendingQueue !== null) {
    // ... 将 pending queue 合并到 base queue
  }
  // 处理更新队列
  if (baseQueue !== null) {
    const first = baseQueue.next
    let newState = current.baseState

    let newBaseState = null
    let newBaseQueueLast = null
    let update = first
    // 循环计算新状态
    do {
      const updateLane = update.lane
      // updateLane 的优先级不在当前渲染阶段的优先级范围内时
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        // ...
        markSkippedUpdateLanes(updateLane) // 将被跳过的更新标记起来,以便在后续的渲染过程中重新处理它们
      } else {
        // ...

        if (update.eagerReducer === reducer) {
          newState = update.eagerState
        } else {
          // 计算新状态
          const action = update.action
          newState = reducer(newState, action) // 执行 reducer,得到最新 states
        }
      }
      update = update.next
    } while (update !== null && update !== first)

    // ...

    if (!is(newState, hook.memoizedState)) {
      // 标记在当前渲染周期中接收到了更新
      markWorkInProgressReceivedUpdate() // 该函数只是将 didReceiveUpdate 全局变量设置为 true
    }
    // 更新 memoizedState 的值
    hook.memoizedState = newState
    hook.baseState = newBaseState
    hook.baseQueue = newBaseQueueLast
    queue.lastRenderedState = newState
  }

  // 返回最新的 state 和更新 state 的函数
  const dispatch = queue.dispatch
  return [hook.memoizedState, dispatch]
}

上述代码只保留一些关键的逻辑,再来看下 updateWorkInProgressHook 的作用:构建 hooks 链表并按顺序复用上一次的 hook 状态信息,确保 currentHookworkInProgressHook 有正确的指向。

这里要知道一个源码细节帮助理解,就是上面我们提到的,当组件更新的时候,currentHookworkInProgressHook 都会被重置为 null

function updateWorkInProgressHook() {
  let nextCurrentHook
  // 当前 fiber 的第一个 Hook
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate
    if (current !== null) {
      nextCurrentHook = current.memoizedState
    } else {
      nextCurrentHook = null
    }
  } else {
    nextCurrentHook = currentHook.next
  }

  let nextWorkInProgressHook
  // 当前 fiber 的第一个 Hook
  if (workInProgressHook === null) {
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState
  } else {
    nextWorkInProgressHook = workInProgressHook.next
  }

  if (nextWorkInProgressHook !== null) {
    workInProgressHook = nextWorkInProgressHook
    nextWorkInProgressHook = workInProgressHook.next
    currentHook = nextCurrentHook
  } else {
    currentHook = nextCurrentHook
    // 生成新的 hook 对象,复用 currentHook
    const newHook = {
      memoizedState: currentHook.memoizedState, // 上次渲染时所用的 state
      baseState: currentHook.baseState, // 一次更新中,产生的最新 state
      baseQueue: currentHook.baseQueue, // 最新的 update 队列
      queue: currentHook.queue, // 当前 update 队列
      next: null,
    }
    // 第一个 Hook
    if (workInProgressHook === null) {
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook
    } else {
      workInProgressHook = workInProgressHook.next = newHook
    }
  }
  return workInProgressHook
}

useReducer

mountReducer

mountReducer 的实现思路与 mountState 大致相同,区别主要在于 mountState 有默认 reducer 参数 basicStateReducer,而 mountReducer 的 reducer 则需依赖外部传入。

function mountReducer(reducer, initialArg, init) {
  const hook = mountWorkInProgressHook() // 创建 Hook
  let initialState
  if (init !== undefined) {
    initialState = init(initialArg) // 处理初始 state 的函数(可选)
  } else {
    initialState = initialArg // 第二个参数,指定初始值
  }
  // 初始值赋值给 hook.memoizedState
  hook.memoizedState = hook.baseState = initialState
  // 创建更新队列,将 reducer 和 初始化后的 state 传入
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: initialState,
  })
  // 负责更新 state
  const dispatch = (queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber, queue))
  return [hook.memoizedState, dispatch]
}

updateReducer

同上,useState 部分已经解析了,此处略过。

useEffect

mountEffect

useEffect 挂载时调用的是 mountEffect 方法,该方法是返回 mountEffectImpl 的调用结果。

mountEffectImpl 则是核心函数,它确保组件挂载时,副作用会被正确地执行和管理,主要工作:

  1. 创建 hook 对象,添加到 workInProgressHook 链表
  2. 创建 effect 并添加到 当前 fiber 的 updateQueue 的链表上,并将该 effect 赋值给 hook.memoizedState 属性
function useEffect(create, deps) {
  const dispatcher = resolveDispatcher()
  return dispatcher.useEffect(create, deps)
}

function mountEffect(create, deps) {
  return mountEffectImpl(
    // UpdateEffect 表示更新 Update;PassiveEffect 表示副作用类型为 useEffect
    UpdateEffect | PassiveEffect,
    HookPassive,
    create, // useEffect 第一个参数,即副作用函数
    deps
  )
}

function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
  // 1. 创建 hook 对象,添加到 workInProgressHook 链表
  const hook = mountWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps
  // 给当前 fiber 打上 "UpdateEffect | PassiveEffect" 标记
  currentlyRenderingFiber.flags |= fiberFlags
  // 2. 创建 effect 并添加到 当前 fiber 的 updateQueue 的链表上,最后将该 effect 赋值给 hook.memoizedState 属性
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    undefined, // 首次挂载 destroy 参数传 undefined
    nextDeps
  )
}

useState 源码的 hook.memoizedState 存的是 state 值,而 useEffect 存的是 pushEffect 函数的返回结果,即 effect 对象(链表)

pushEffect 函数主要工作:创建 effect,并添加到 updateQueue 链表,最后返回 effect

function pushEffect(tag, create, destroy, deps) {
  // 1. 创建 effect
  const effect = {
    tag, // 用于区分 useEffect 和 useLayoutEffect
    create,
    destroy, // mountEffectImpl 该参数传的是 undefined
    deps,
    next: null,
  }
  // 2. 将 effect 添加到 updateQueue 链表
  let componentUpdateQueue = currentlyRenderingFiber.updateQueue
  // fiber 节点不存在时则初始化 updateQueue
  if (componentUpdateQueue === null) {
    // 创建新的 updateQueue
    componentUpdateQueue = createFunctionComponentUpdateQueue() // 返回 { lastEffect: null }
    currentlyRenderingFiber.updateQueue = componentUpdateQueue
    componentUpdateQueue.lastEffect = effect.next = effect
  } else {
    const lastEffect = componentUpdateQueue.lastEffect
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect
    } else {
      // 构成单向环形链表:lastEffect 为最新的 effect,lastEffect.next 为第一个 effect
      const firstEffect = lastEffect.next
      lastEffect.next = effect
      effect.next = firstEffect
      componentUpdateQueue.lastEffect = effect
    }
  }
  return effect
}

updateEffect

更新时调用的核心方法是 updateEffectImpl,主要工作是:

  1. 获取当前 fiber 对象的 hook 信息
  2. 对比新旧依赖项,如果依赖项不变则创建 effect 到链表中(但最终并不会执行, tag 不含 HookHasEffect);不相等则继续执行,最终也会创建 effect 到链表中(tag 含 HookHasEffect)
  3. 依赖项变化时:给当前 fiber 打上 PassiveEffect,表示存在需要执行的 useEffect;并在创建 effect 时传入 HookHasEffect,表示有副作用,commit 阶段则会执行 effect 的回调和销毁函数
function updateEffect(create, deps) {
  return updateEffectImpl(UpdateEffect | PassiveEffect, HookPassive, create, deps)
}

// 这里传进来的 fiberFlags 值为 UpdateEffect | PassiveEffect(给 fiber 使用的标记)
// 这里传进来的 hookFlags 表示 Passive EffectTag (标识是 useEffect 还是 useLayoutEffect)
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
  const hook = updateWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps
  let destroy = undefined

  if (currentHook !== null) {
    // 上一次的 effect 对象
    const prevEffect = currentHook.memoizedState
    destroy = prevEffect.destroy
    // 有依赖项
    if (nextDeps !== null) {
      // 上一次的依赖数组
      const prevDeps = prevEffect.deps
      // 新旧依赖相等
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 相等也把 effect 加入链表,确保顺序一致
        pushEffect(hookFlags, create, destroy, nextDeps) // effect tag 少了 HookHasEffect,后续处理看做无更新
        return
      }
    }
  }
  // 给当前 fiber 打上 "UpdateEffect | PassiveEffect" 标记
  currentlyRenderingFiber.flags |= fiberFlags

  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags, // HookHasEffect 表示有副作用,这里 hookFlags 传值为 Passive,两者合计表示 useEffect 有副作用
    create, // 回调函数
    destroy, // 销毁函数
    nextDeps // 当前最新的依赖项
  )
}

// 用于判断两个依赖数组的值是否相等
function areHookInputsEqual(nextDeps, prevDeps) {
  if (prevDeps === null) {
    return false
  }

  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    // Object.is 判断是否相等
    if (is(nextDeps[i], prevDeps[i])) {
      continue
    }
    return false
  }
  return true
}

useLayoutEffect

useEffect 和 useLayoutEffect 底层复用的都是同个函数,只不过第一个和第二个传参不一样,依靠 fiber.flagseffect.tag 实现对 effect 的识别。

两者最大的区别是 useEffect为异步执行,而 useLayoutEffect 为同步执行。

  1. fiber.flags 不同
    • useEffect:值为 UpdateEffect | PassiveEffect
    • useLayoutEffect:值为 UpdateEffect
  2. effect.tag 不同
    • useEffect:值为 HookHasEffect | HookPassive
    • useLayoutEffect:值为 HookHasEffect | HookLayout
function mountLayoutEffect(create, deps) {
  return mountEffectImpl(UpdateEffect, HookLayout, create, deps)
}

function updateLayoutEffect(create, deps) {
  return updateEffectImpl(UpdateEffect, HookLayout, create, deps)
}

useRef

useRef 的实现是最容易读懂的。

  • mountRef 就是生成一个对象并返回,结构为 { current: 值 }current 属性来保存初始化的值
  • updateRef 则直接返回缓存在 hook.memoizedState 的 ref 对象
function mountRef(initialValue) {
  const hook = mountWorkInProgressHook()
  const ref = { current: initialValue } // 创建ref对象
  hook.memoizedState = ref // ref 对象赋值给 memoizedState,保存的是内存地址
  return ref // 返回 ref 对象,当 current 属性发生变化时,组件不会重新渲染
}

function updateRef(initialValue) {
  const hook = updateWorkInProgressHook()
  // 直接返回缓存在 hook.memoizedState 的 ref 对象
  return hook.memoizedState
}

useCallback

  • mountCallback:将 useCallback 的回调函数与依赖项以数组形式保存到 hook.memoizedState
  • updateCallback:判断新旧依赖项是否相等,相等则取出上一次的hook.memoizedState 的缓存值,返回上一次回调函数的引用;不相等则返回新传入的回调函数
function mountCallback(callback, deps) {
  const hook = mountWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps
  // memoizedState 缓存 useCallback 的两个参数的引用数组
  hook.memoizedState = [callback, nextDeps]
  return callback
}

function updateCallback(callback, deps) {
  const hook = updateWorkInProgressHook()
  // 获取更新时的依赖项
  const nextDeps = deps === undefined ? null : deps
  // 取出上一次的数组值
  const prevState = hook.memoizedState
  if (prevState !== null) {
    if (nextDeps !== null) {
      // 取出第二个参数依赖项数组
      const prevDeps = prevState[1]
      // 依赖项不变则取出上一次的回调函数
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0] // 返回缓存的函数
      }
    }
  }
  // 依赖项发生变化,返回新传入的函数
  hook.memoizedState = [callback, nextDeps]
  return callback
}

useMemo

  • mountMemo:将 useMemo 的缓存计算结果与依赖项以数组形式保存到 hook.memoizedState
  • updateMemo:判断新旧依赖项是否相等,相等则取出上一次的hook.memoizedState 的缓存值,返回上一次计算结果;不相等则返回执行回调函数,返回回调函数的计算结果。
function mountMemo(nextCreate, deps) {
  const hook = mountWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps
  const nextValue = nextCreate() // 执行函数返回计算结果
  // memoizedState 缓存 useMemo 的两个参数的引用数组
  hook.memoizedState = [nextValue, nextDeps]
  return nextValue
}

function updateMemo(nextCreate, deps) {
  const hook = updateWorkInProgressHook()
  // 获取更新时的依赖项
  const nextDeps = deps === undefined ? null : deps
  const prevState = hook.memoizedState
  if (prevState !== null) {
    if (nextDeps !== null) {
      // 取出第二个参数依赖项数组
      const prevDeps = prevState[1]
      // 依赖项不变则取出上一次的计算结果
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0] // 返回缓存的结果值
      }
    }
  }
  // 依赖项发生变化
  const nextValue = nextCreate() // 重新执行函数返回最新计算结果
  hook.memoizedState = [nextValue, nextDeps]
  return nextValue
}

看上面代码,可以看出 useMemouseCallback 实现思路几乎一致,区别在于 useMemo 需要会将外部传入的函数 nextCreate() 直接执行,这与两者的用法相关。

总结

hooks 实现是基于 fiber 的,通常链表形式串起来。每个 fiber 节点的 memoizedState 保存了对应的数据,不同的 hooks 通过使用该对应数据完成对应的逻辑功能。

  • useState/useReducermemoizedState 等于 state 的值
  • useEffectmemoizedState 保存的是 effect 链表(包含 useEffect 第一个参数回调与第二个参数依赖项数组的值)
  • useRefmemoizedState 等于 { current: 当前值 }
  • useCallback:保存两个参数值,memoizedState 等于 [callback, deps],缓存的是函数
  • useMemo:保存两个参数值,memoizedState 等于 [callback(), deps],缓存的是函数执行计算结果

参考文章