ZHANGYU.dev

October 14, 2023

useState和useReducer源码浅析

React13.2 min to read

以下源码浅析的React版本为17.0.1,使用ReactDOM.render创建的同步应用,不含优先级相关。

流程简述

函数组件会调用renderWithHooks函数,这个函数主要会标记当前渲染的currentlyRenderingFiber节点,并判断该使用哪一个HooksDispatcher(React里Mount和Update所使用的Hooks不是同一个),接着执行此函数组件,分别处理hook函数,并得到children,将children返回到上层函数后,执行reconcileChildren生成child子节点。

renderWithHooks

renderWithHooks函数为函数组件的入口函数,无论是Mount时的mountIndeterminateComponent还是Update时的updateFunctionComponent都会进入这个函数来获取函数组件的children

export function renderWithHooks<Props, SecondArg>(  current: Fiber | null,  workInProgress: Fiber,  Component: (p: Props, arg: SecondArg) => any,  props: Props,  secondArg: SecondArg,  nextRenderLanes: Lanes,): any {  // 省略代码...  currentlyRenderingFiber = workInProgress;  // 使用mount还是update的hook  ReactCurrentDispatcher.current =    current === null || current.memoizedState === null      ? HooksDispatcherOnMount      : HooksDispatcherOnUpdate;  // 省略代码...    // 执行函数后返回的children  let children = Component(props, secondArg);  // 省略代码...  ReactCurrentDispatcher.current = ContextOnlyDispatcher;  // 省略代码...    return children;}

renderWithHooks中会将当前的workInProgressFiber节点存在全局变量currentlyRenderingFiber中,这样方便后面获取Fiber信息,接着在调用函数获取children之前,先判断使用哪一个ReactCurrentDispatcher,最后返回children给上层函数处理。

需要注意的是Mount时和Update时用的不是同一个hook。

const HooksDispatcherOnMount: Dispatcher = {  // ...  useReducer: mountReducer,  useState: mountState,};const HooksDispatcherOnUpdate: Dispatcher = {  // ...  useReducer: updateReducer,  useState: updateState,};

再来看下useState

export function useState<S>(  initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {  const dispatcher = resolveDispatcher();  return dispatcher.useState(initialState);}// 返回的是调用函数组件之前赋值的ReactCurrentDispatcher.currentfunction resolveDispatcher() {  const dispatcher = ReactCurrentDispatcher.current;  return dispatcher;}

所以后续会分为Mount和Update来分析

Hooks的数据结构

单个hook的数据结构如下

type Hook = {|  memoizedState: any, // 对于useState和useReducer的值就是state的值  baseState: any,  baseQueue: Update<any, any> | null,  queue: UpdateQueue<any, any> | null, // 更新队列  next: Hook | null, // 下一个hook节点|}

那函数组件如何找到对应的hooks信息呢?函数组件的hooks的信息储存在对应Fiber节点中的memoizedState字段中,代表函数组件里的第一个hookhooks数据结构为单向链表,每一个节点可以通过next属性找到下一个hook

function Container(){  React.useState() // currentlyRenderingFiber.memoizedState (数据结构即是上面的Hook type)  React.useState() // currentlyRenderingFiber.memoizedState.next (Hook.next) // ...}

每一个hook的更新队列都会存在queue字段里,通过执行队列里的操作就可以得到最新的state值,结构如下

type UpdateQueue<S, A> = {|  pending: Update<S, A> | null, // 最新的更新任务  dispatch: (A => mixed) | null, // 也就是useState的第二个参数用来发起更新  lastRenderedReducer: ((S, A) => S) | null, // 上一次的reducer  lastRenderedState: S | null, // 上次的state值|};type Update<S, A> = {|  lane: Lane,  action: A,  eagerReducer: ((S, A) => S) | null,  eagerState: S | null, // 第一次调用dispatch时赋值  next: Update<S, A>, // 下一个更新  priority?: ReactPriorityLevel,|};

hook的更新队列是一个单向环形链表,pending字段保存的是最新的update,通过update.next可以获取第一个加入更新队列的update

update2(pending) -> update0 -> update1 -> update2

useState和useReducer

useState实际是一个自带了reduceruseReducer语法糖,所以需要放在一起分析。

mountState

function mountState<S>(  initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {  // 创建一个新的hook结构,如果workInProgressHook已经存在就用next连接起来  const hook = mountWorkInProgressHook();  // useState的初始值  if (typeof initialState === 'function') {    initialState = initialState();  }  hook.memoizedState = hook.baseState = initialState;  const queue = (hook.queue = {    pending: null,    dispatch: null,    lastRenderedReducer: basicStateReducer, // 自带了一个basicStateReducer    lastRenderedState: (initialState: any),  });  const dispatch: Dispatch<    BasicStateAction<S>,  > = (queue.dispatch = (dispatchAction.bind(    null,    currentlyRenderingFiber,    queue,  ): any));  return [hook.memoizedState, dispatch];}// useState自带的reducerfunction basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {  return typeof action === 'function' ? action(state) : action;}

mountState自带了一个basicStateReducer

mountReducer

function mountReducer<S, I, A>(  reducer: (S, A) => S,  initialArg: I,  init?: I => S,): [S, Dispatch<A>] {  const hook = mountWorkInProgressHook();  let initialState;  // useReducer的初始值  if (init !== undefined) {    initialState = init(initialArg);  } else {    initialState = ((initialArg: any): S);  }  hook.memoizedState = hook.baseState = initialState;  const queue = (hook.queue = {    pending: null,    dispatch: null,    lastRenderedReducer: reducer, // 传入的reducer    lastRenderedState: (initialState: any),  });  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(    null,    currentlyRenderingFiber,    queue,  ): any));  return [hook.memoizedState, dispatch];}

mountStatemountReducer的区别在于函数参数和初始值赋值的不同,其他都是一样的。

进入函数首先会通过mountWorkInProgressHook来创建hook对象,接着初始化queue,通过bind将Fiber节点和queue传入dispatchAction函数实现部分参数,最后将值返回。

mountWorkInProgressHook

mountWorkInProgressHook函数功能比较简单,创建一个hook对象,将多个hook对象连接为单向链表。

function mountWorkInProgressHook(): Hook {  // 创建新的hook对象  const hook: Hook = {    memoizedState: null,    baseState: null,    baseQueue: null,    queue: null,    next: null,  };  if (workInProgressHook === null) {    // workInProgressHook为null,则当前hook为函数里第一个hook,赋值给Fiber节点的memoizedState    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;  } else {    // 后续的hook就通过next来连接    workInProgressHook = workInProgressHook.next = hook;  }  return workInProgressHook;}

dispatchAction

dispatchAction方法为用来创建一个新的update,来发起更新

/* mountReducer ...const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(  null,  currentlyRenderingFiber,  queue,): any));*/// 每次调用之前会bind上fiber和queue参数,只需要传入actionfunction dispatchAction<S, A>(  fiber: Fiber,  queue: UpdateQueue<S, A>,  action: A,) {  // 省略代码...    const update: Update<S, A> = {    lane,    action,    eagerReducer: null,    eagerState: null,    next: (null: any),  };  // 环型链表  // 将update添加到链表最后  const pending = queue.pending;  if (pending === null) {    // pending为null的时候是第一次调用dispatchAction发起更新    // 创建一个环形链表    update.next = update;  } else {    // 将后续添加的update    update.next = pending.next;    pending.next = update;  }  // pending的值为最新的update  queue.pending = update;  const alternate = fiber.alternate;  // 判断是否为render阶段的更新  if (    fiber === currentlyRenderingFiber ||    (alternate !== null && alternate === currentlyRenderingFiber)  ) {    // 标记    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;  } else {    // 通过优先级判断是否为第一次更新(?)    if (      fiber.lanes === NoLanes &&      (alternate === null || alternate.lanes === NoLanes)    ) {      // 当前没有更新队列,是第一次调用dispatchAction产生update,所以可以在进入render阶段之前就计算出state      const lastRenderedReducer = queue.lastRenderedReducer;      if (lastRenderedReducer !== null) {        try {          const currentState: S = (queue.lastRenderedState: any);          const eagerState = lastRenderedReducer(currentState, action);          // 在render阶段判断reducer如果没变化就直接取eagerState的值          update.eagerReducer = lastRenderedReducer;          update.eagerState = eagerState;          if (is(eagerState, currentState)) {            // 如果结果和当前state值一致,就不需要发起更新调度了,如果不一致,则可以在render阶段获取eagerState来直接取值而不需要再次计算            return;          }        } catch (error) {          // 错误会在render阶段抛出        }       }    }    // 发起更新调度    scheduleUpdateOnFiber(fiber, lane, eventTime);  }}

dispatchAction里会把更新连接进环形链表,如果不是render阶段的更新则会通过优先级判断Fiber节点上是否存在更新,如果不存在就会在dispatchAction里计算出新的state值,接着判断新旧值是否相同,相同就不需要发起更新调度了。

当同步调用多次dispatchAction就会产生多个update,会将他们组成环形链表。

function Updater(){  const render = useState(0)[1];  return <div onClick={()=>{      render(prev => prev+1);      render(prev => prev+1);      render(prev => prev+1);    }}>update</div>}

这里的环形链表连接比较难理解

  const pending = queue.pending;  if (pending === null) {    update.next = update;  } else {    update.next = pending.next;    pending.next = update;  }  queue.pending = update;

第一次执行的时候,pending === null,会创建一个自己连接自己的环形链表,这里表示为u0 -> u0

第二次执行的时候,pending !== null,创建一个新的更新u1

第三次执行的时候,创建一个新的更新u2

这里仔细思考,实际上只改变了头和尾,中间的连接(u0 - > u1)没有改变,所以最后的结果为queue.pending(u2) -> u0 -> u1 -> u2

最终的环形链表的pending始终指向最新的update,而最新的update.next指向第一个更新u0

updateState

updateState调用的就完全是updateReducer了,只是传入了自带的reducer,所以updateStateupdateReducer可谓是完全一致

function updateState<S>(  initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {  return updateReducer(basicStateReducer, (initialState: any));}

updateReducer

mountReducer返回的值是initialStateupdateReducer返回的值则是通过调用依次queue中的update计算后的state值。

function updateReducer<S, I, A>(  reducer: (S, A) => S,  initialArg: I,  init?: I => S,): [S, Dispatch<A>] {  // update阶段对应的hook  const hook = updateWorkInProgressHook();  const queue = hook.queue;  queue.lastRenderedReducer = reducer;  // 与workInProgressHook对应的currentHook  const current: Hook = (currentHook: any);  let baseQueue = current.baseQueue;  const pendingQueue = queue.pending;  if (pendingQueue !== null) {    if (baseQueue !== null) {      // 省略代码...    }    // pendingQueue赋值给baseQueue    current.baseQueue = baseQueue = pendingQueue;    queue.pending = null;  }  if (baseQueue !== null) {    // queue.pending为最新的update,next则为第一个update    const first = baseQueue.next;    let newState = current.baseState;    let newBaseState = null;    let newBaseQueueFirst = null;    let newBaseQueueLast = null;    let update = first;    do {      const updateLane = update.lane;      if (!isSubsetOfLanes(renderLanes, updateLane)) {        // 省略优先级相关代码...      } else {        if (newBaseQueueLast !== null) {          // 省略代码...        }        // update.eagerReducer只有在第一次调用dispatchAction发起更新的时候才会赋值        // 当reducer没有发生变化的时候        if (update.eagerReducer === reducer) {          newState = ((update.eagerState: any): S);        } else {          // 不是第一次调用dispatchAction就计算新的state          const action = update.action;          newState = reducer(newState, action);        }      }      // 指向下一个更新      update = update.next;    } while (update !== null && update !== first);    if (newBaseQueueLast === null) {      newBaseState = newState;    } else {      newBaseQueueLast.next = (newBaseQueueFirst: any);    }        // 值不同才标识变化    if (!is(newState, hook.memoizedState)) {      markWorkInProgressReceivedUpdate();    }    // 赋值新的state    hook.memoizedState = newState;    hook.baseState = newBaseState;    hook.baseQueue = newBaseQueueLast;    queue.lastRenderedState = newState;  }  const dispatch: Dispatch<A> = (queue.dispatch: any);  return [hook.memoizedState, dispatch];}

updateReducer会依次将queue中的update放入reducer中计算,最后将新的state值赋值给hook.memoizedState并返回。

updateWorkInProgressHook

updateWorkInProgressHookmountWorkInProgressHook功能相似,会返回一个浅拷贝的hook对象,更改currentHookworkInProgressHook的指向,同时连接一个新的workInProgressHook链表。

function updateWorkInProgressHook(): Hook {    let nextCurrentHook: null | Hook;  // 第一次进入fiber节点的时候执行hook没有hook指向  if (currentHook === null) {    // 找到alternate fiber节点的memoizedState hook对象    const current = currentlyRenderingFiber.alternate;    if (current !== null) {      nextCurrentHook = current.memoizedState;    } else {      nextCurrentHook = null;    }  } else {    // 后续执行hook的alternate hook对象    nextCurrentHook = currentHook.next;  }  let nextWorkInProgressHook: null | Hook;  // 省略代码...  if (nextWorkInProgressHook !== null) {    // 省略代码...  } else {    currentHook = nextCurrentHook;    // 通过currentHook进行浅拷贝    const newHook: Hook = {      memoizedState: currentHook.memoizedState,      baseState: currentHook.baseState,      baseQueue: currentHook.baseQueue,      queue: currentHook.queue,      next: null,    };    // 组装workInProgressHook链表    if (workInProgressHook === null) {      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;    } else {      workInProgressHook = workInProgressHook.next = newHook;    }  }  return workInProgressHook;}

总结

函数组件通过renderWithHooks可以确定当前的WorkInProgressFiber节点,通过是否存在currentFiber节点来判断当前为Mount还是Update,分别获取不同的ReactCurrentDispatcher,执行函数组件自己来获取children

执行过程中,同时会执行到对应的hook函数,函数组件的hooks为单向链表存在Fiber节点的memoizedState字段上,通过hook.next可以顺序依次获取hook对象,每一个hook对象中存在memoizedState字段,对于useStateuseReducer来说储存的即为state值本身,hook对象上存在queue代表当前hook的更新队列,为环形单向链表,queue.pending指向为最新的updatequeue.pending.next执行为第一个update

通过执行mountStatemountReducer来获取state初始值,通过执行updateStateupdateReducer来计算queue中的update以获取最新的state值。

调用dispatchAction发起更新调度,同时在dispatchAction里会组装更新的queue环形单向链表,最后在render阶段会执行updateStateupdateReducer来获取最新的state值。

如有错误,还望交流指正。