React源码解析(六):useState与useReducer源码解析

lxf2023-04-16 07:39:01

useState 和 useReducer 可以说是 React 最基础也是最常用的两个 Hook 了,在介绍这两个钩子函数源码之前,先引入几个源码当中的变量,以便大家更好的理解。

  • currentlyRenderingFiber 是当前 React 正在处理的 Fiber 对象,等同于 workInProgress代码中为了更好的区分 workInProgressworkInProgressHook,使用了 currentlyRenderingFiber
  • 一个 Fiber 对象所有的 Hook 会以链表的形式被保存在其 memoizedState 属性上
  • currentHook 全局属性指 workInProgress 将要替换的 Fiber 的 Hook 链表,即 workInProgress.alternate.memoizedState,也就是 current.memoizedState
  • workInProgressHook 全局属性指向当前 workInProgressHook 链表,即 workInProgress.memoizedState
  • 虽然 workInProgress.memoizedStatecurrent.memoizedState 都以 State 结尾,但都是 Hook 链表,前者会根据后者生成,因此每次更新 Fiber 时链表节点(单个Hook对象)需要前后对应上,前后链表节点 baseStatebaseQueue 属性也存在逻辑依赖。
几个重要的概念:
  • State

    我们在编写 React 组件时交给 React 存储的状态,它们会被挂载在当前组件所对应的 Fiber 上,具体在 fiber.memoizedState[第n个链表节点].memoizedState 上。

  • Action

    action 在 Redux 中是一个携带描述变化信息的普通对象,而在 React 内部的状态管理中比较直观,它是我们在 dispatcher 中传入的新的 state 值或者可返回新 state 值的函数。

  • Reducer

    reducer 在 Redux 中是一个纯函数,用于接收旧的 actionstate,返回新的 state,而在 React 同样也是起这样的作用,React 在 updateStatererenderState 中传入的 basicStateReducer 是这样的:

    function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
        return typeof action === 'function' ? action(state) : action;
    }
    
  • Dispatcher

    React 内部的 dispatcher 派发器跟状态管理库 Flux 的 dispatcher 类似,接受一个 action,并间接触发 React 组件状态的更新,它也是 useStateuseReducer 返回给用户,用于更新状态的 setXxxx 函数。

useState & useReducer

useState 唯一参数就是初始状态 initialState,或者是返回初始状态 initialState 的函数;useReducer 可以接受三个参数,第一个是 reducer,第二个参数是初始状态 initialState,第三个可选参数是一个初始化初始状态 initialState 的函数;

useState<S>(
    initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>]
    
useReducer<S, I, A>(
    reducer: (S, A) => S,
    initialArg: I,
    init?: I => S,
): [S, Dispatch<A>]

函数组件中可以通过它们给组件添加状态,具体状态管理流程如下: React源码解析(六):useState与useReducer源码解析

React源码解析(六):useState与useReducer源码解析

React 会维护一个 ReactCurrentDispatcher 派发器对象,此对象 current 属性会实时指向当前的具体 dispatcher,current 的值一般有以下四种:

const ContextOnlyDispatcher: Dispatcher = {
    // 当我们在函数外面调用 hook 函数会报错,执行的就是这个`dispatcher`
    useState: throwInvalidHookError,
    useReducer: throwInvalidHookError
}
const HooksDispatcherOnMount: Dispatcher = {  
    useState: mountState,
    useReducer: mountReducer
};
const HooksDispatcherOnUpdate: Dispatcher = {  
    useState: updateState,
    useReducer: updateReducer
};
const HooksDispatcherOnRerender: Dispatcher = {  
    useState: rerenderState,
    useReducer: rerenderReducer
};
// 排除了在DEV环境下的情况 ...
mountState & mountReducer
  1. mountStatemountReducer 首先会调用 mountWorkInProgressHook 创建一个 hook 对象,将当前 React 正在处理的 workInProgress 对象 memoizedState 属性指向该 hook 对象;

    const hook: Hook = {
        memoizedState: null, // hook对象当中也有一个memoizedState
        baseState: null,
        baseQueue: null,
        queue: null,
        next: null,
    };
    
  2. mountStatemountReducerhook 对象 memoizedStatebaseState 两个属性来源不同,mountState 中是我们传入的初始状态 initialState(也可以是 action 函数),而在 mountReducer 中是我们传入的第二个参数,但如果有第三个参数,那么会通过第三个参数初始化第二个参数得到初始状态 initialState,即 initialState = init(initialArg)

  3. 初始化 hook 对象 queue 属性,其中 queue.lastRenderedState 属性会指向初始状态 initialStatequeue.lastRenderedReducer 属性在 mountState 中指向全局函数 basicStateReducer,而在 mountReducer 中指向 useReducer 传入的第一个参数(reducer 函数)

    const queue: UpdateQueue<S, A | BasicStateAction<S>> = {
        pending: null,
        interleaved: null,
        lanes: NoLanes,
        dispatch: null,
        lastRenderedReducer: basicStateReducer, // mountState
        lastRenderedReducer: reducer, // mountReducer
        lastRenderedState: (initialState: any),
    };
    hook.queue = queue;
    
  4. hook.queue 对象 dispatch 属性指向 dispatchAction.bind(null, workInProgress, queue)

  5. 返回一个小数组,第一个元素为 hook.memoizedState,第二个元素为 hook.queue.dispatch

updateState & updateReducer
  1. updateState 会直接调用 updateReducer,传入 basicStateReducer 和初始状态 initialState 值;

    function updateState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateA  ction<S>>] {
      return updateReducer(basicStateReducer, (initialState: any));
    }
    
  2. updateReducer 首先会调用 updateWorkInProgressHook,此函数内部会从 current 树中拿到下一个 currentHookworkInProgressHook,返回 workInProgressHook;

  3. 如果 workInProgressHook.queue.pending(源码中变量 pendingQueue 指向此链表)不为null,则将 pendingQueue 插入到 currentHook.baseQueue 第一个链表节点前,最终将 pendingQueue 设为 null;

    let baseQueue = current.baseQueue;
    const pendingQueue = queue.pending;
    
    // Merge the pending queue and the base queue.
    const baseFirst = baseQueue.next;
    const pendingFirst = pendingQueue.next;
    
    baseQueue.next = pendingFirst;
    pendingQueue.next = baseFirst;
    
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
    
  4. 如果 baseQueue 不为null,则从 baseQueue 第一个链表节点开始向后遍历,每个节点都是一次 dispatcher 新的 update,判断该 update 的优先级 update.lane 是否包含在 renderLanes 中:不包含会跳过更新,将未更新的 update 保存在 baseQueue 中,其优先级 update.lane 合并到 workInProgress.lane 中,等待下一次更新;包含则调用 basicStateReducer 获取新的 state;

  5. 最终更新 workInProgressHookmemoizedStatebaseStatebaseQueue 属性,workInProgressHook.queue.lastRenderedState 也为新的 state;

  6. 返回一个小数组,数组第一项为 workInProgressHook.memoizedState,数组第二项为 workInProgressHook.queue.dispatch 对象。

rerenderState & renderReducer
  1. rerenderState 会直接调用 rerenderReducer,传入 basicStateReducer 和传入的初始状态 initialState 值;

    function rerenderState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      return rerenderReducer(basicStateReducer, (initialState: any));
    }
    
  2. rerenderReducer 函数作用跟 updateReducer 函数差不多,只有两个不同点:

    • 会检查 workInProgressHook.queue.pending(源码中 lastRenderPhaseUpdate 指向此链表),如果不为 null,则对 lastRenderPhaseUpdate 进行向后遍历,调用 basicStateReducer 获取新的 state;
    • 在遍历时不检查 lastRenderPhaseUpdate 中每个 update 的优先级,因为此时的 update.lane 肯定会包含在 renderLanes 中;

更新状态的函数:dispatcher

我们通常定义为 setXxxx,即 useStateuseReducer 返回的数组第二项,是一个 dispatcher 函数,调用该函数会在一定条件下引发 React 组件的更新, React 的调度器 scheduler 也会参与这个过程。因为需要调度更新的优先级,此过程是异步的,最快会在下一次 React 组件函数执行时,useStateuseReducer 才能返回新的 state

  1. 首先这个函数会创建一个 update,如果断定当前的 Fiber 正在 render 阶段,则将 update 插入到此 Hook 的 queue.pending 链表中,并更新两个全局对象:

    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
    
  2. 如果当前的 Fiber 不在 render 阶段:

    • 则根据是不是 ConcurrentMode 选择要插入到 queue.interleaved 链表中还是 queue.pending 链表中;
    • 此时如果当前 Fiber 没有任何优先级的更新需要处理,则提前通过 reducer 得到下一个状态值,并将此时的 reducer 和新的状态值隐藏在第一步创建的 update 中,如果新的状态值和旧的状态值一样,则直接结束 dispatch 过程;
    • 之后会调用 scheduleUpdateOnFiber 函数,当前 Fiber 节点进入 render 阶段;
  3. render 阶段会调用 React 组件函数,此时 useState 就可以拿到 workInProgressHook.queue.pending 上的每个新产生的 update 进行上文所说的处理。