React 核心 Hooks useReducer 实现原理和 Fiber 更新调用链路

137 阅读6分钟

React 核心 Hooks useReducer 实现原理和 Fiber 更新调用链路

React Hooks 中涉及到组件状态维护的 Hooks 包括 useState、useReducer。useState 是 useReducer 的简单语法糖。以下是分析 useReducer 的实现原理,以及 dispatch 后 Fiber 更新调用的链路分析:

React useState 和 useReducer 的实现原理

React 中 useStateuseReducer 的实现共享底层逻辑,useState 本质上是 useReducer 的一个简化版本。两者都通过 Fiber 架构的更新队列优先级调度机制 实现状态更新控制。以下是源码层面的详细分析:


1. useStateuseReducer 的语法糖

在 React 源码中,useState 直接调用了 useReducer,并传入一个预定义的 basicStateReducer

// ReactHooks.js
import { useReducer } from './ReactFiberHooks';

function useState(initialState) {
  return useReducer(basicStateReducer, initialState);
}

function basicStateReducer(state, action) {
  return typeof action === 'function'? action(state) : action;
}
  • basicStateReducer 的作用
    • 如果 action 是函数(如 setCount(c => c + 1)),则调用该函数并传入当前状态,返回新状态。
    • 如果 action 是直接值(如 setCount(5)),则直接返回该值作为新状态。

2. useReducer 的通用性

useReducer 的实现逻辑主要在 ReactFiberHooks.js 文件中。以下是简化后的核心代码:

// ReactFiberHooks.js
let currentlyRenderingFiber = null; // 当前正在渲染的 Fiber 节点
let workInProgressHook = null; // 当前正在处理的 Hook

function useReducer(reducer, initialState) {
  const hook = updateWorkInProgressHook();

  if (hook.memoizedState === null) {
    // 初始化状态
    if (typeof initialState === "function") {
      hook.memoizedState = initialState();
    } else {
      hook.memoizedState = initialState;
    }
    hook.baseState = hook.memoizedState;
    hook.queue = {
      pending: null,
      interleaved: null,
      lanes: NoLanes,
    };
  }

  const dispatch = (action) => {
    const update = {
      action,
      next: null,
    };

    // 将更新对象添加到更新队列中
    const queue = hook.queue;
    if (queue.pending === null) {
      update.next = update;
    } else {
      update.next = queue.pending.next;
      queue.pending.next = update;
    }
    queue.pending = update;

    // 触发调度更新
    scheduleUpdateOnFiber(currentlyRenderingFiber);
  };

  // 处理更新队列
  const { baseState, queue } = hook;
  let firstUpdate = queue.pending;
  if (firstUpdate !== null) {
    let newState = baseState;
    let update = firstUpdate;
    do {
      const action = update.action;
      newState = reducer(newState, action);
      update = update.next;
    } while (update !== firstUpdate);

    hook.memoizedState = newState;
    hook.baseState = newState;
    queue.pending = null;
  }

  return [hook.memoizedState, dispatch];
}

function updateWorkInProgressHook() {
  let nextCurrentHook;
  if (workInProgressHook === null) {
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    nextCurrentHook = workInProgressHook.next;
  }

  let newHook;
  if (nextCurrentHook === null) {
    // 新的 Hook
    newHook = {
      memoizedState: null,
      baseState: null,
      baseQueue: null,
      queue: null,
      next: null,
    };
  } else {
    // 复用现有的 Hook
    newHook = {
      memoizedState: nextCurrentHook.memoizedState,
      baseState: nextCurrentHook.baseState,
      baseQueue: nextCurrentHook.baseQueue,
      queue: nextCurrentHook.queue,
      next: null,
    };
  }

  if (workInProgressHook === null) {
    currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
  } else {
    workInProgressHook = workInProgressHook.next = newHook;
  }

  return newHook;
}

初始化阶段
  • updateWorkInProgressHook 函数用于获取当前正在处理的 Hook 对象。如果是首次渲染,会创建一个新的 Hook 对象,并将其添加到当前 Fiber 节点的 memoizedState 链表中。
  • 在 useReducer 函数中,如果 hook.memoizedState 为 null,表示是首次渲染,会根据 initialState 的类型初始化状态,并创建一个空的更新队列。
状态更新阶段
  • 当调用 dispatch 函数(即 setState)时,会创建一个更新对象 update,并将其添加到 Hook 的更新队列 queue 中。更新队列是一个循环链表,通过 pending 指针指向最后一个更新对象。
  • 调用 scheduleUpdateOnFiber 函数触发 React 的调度更新流程,通知 React 该组件需要重新渲染。
重新渲染阶段
  • 在重新渲染时,会处理 Hook 的更新队列。遍历更新队列中的每个更新对象,调用 reducer 函数计算出新的状态值。
  • 更新 hook.memoizedState 和 hook.baseState 为新的状态值,并清空更新队列。

useReducer 的返回

  • 当前状态 hook.memoizedState
  • dispatch 函数(即 setState 或自定义的 dispatch)。

React useReducer 触发更新的源码调用链路分析

从调用 dispatch(action) 到最终组件更新,React 内部经历了 更新调度优先级处理协调(Reconciliation)提交(Commit) 四个核心阶段。以下是基于 React 18 源码的详细调用链路分析:


一、触发更新:dispatch(action)

无论是 useState 还是 useReducer,状态更新均通过以下流程实现,当调用 dispatch 函数(即 useReducer 返回的第二个值)时,React 内部开始处理更新流程:

  1. 创建更新对象(Update)

    // 源码位置:ReactFiberHooks.js
    function dispatchReducerAction(fiber, queue, action) {
      const lane = requestUpdateLane(fiber); // 获取更新优先级(Lane)
      const update = {
        lane,               // 优先级标记(React 18 并发模式核心)
        action,             // 用户传递的 action(如 { type: 'increment' })
        hasEagerState: false,
        eagerState: null,
        next: null,         // 指向下一个更新对象(链表结构)
      };
      enqueueUpdate(fiber, queue, update); // 将 update 加入队列
      scheduleUpdateOnFiber(fiber, lane, eventTime); // 调度更新
    }
    
  2. 将更新加入队列(enqueueUpdate

    // 源码位置:ReactUpdateQueue.js
    function enqueueUpdate(fiber, queue, update) {
      const pending = queue.pending;
      if (pending === null) {
        update.next = update; // 环形链表初始化
      } else {
        update.next = pending.next;
        pending.next = update;
      }
      queue.pending = update; // 更新队列尾部指针
    }
    

二、调度更新:scheduleUpdateOnFiber

调度阶段确定更新的优先级并触发渲染流程:

  1. 标记 Fiber 树的更新优先级

    // 源码位置:ReactFiberWorkLoop.js
    function scheduleUpdateOnFiber(fiber, lane, eventTime) {
      const root = markUpdateLaneFromFiberToRoot(fiber, lane); // 标记优先级到根节点
      if (root === null) return;
      ensureRootIsScheduled(root, eventTime); // 确保根节点被调度
    }
    
  2. 请求调度器执行任务

    // 源码位置:ReactFiberWorkLoop.js
    function ensureRootIsScheduled(root, currentTime) {
      // 根据优先级选择调度方式(同步或并发)
      if (newCallbackPriority === SyncLanePriority) {
        // 同步模式:立即执行
        scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
      } else {
        // 并发模式:请求时间片
        scheduleCallback(
          schedulerPriorityLevel,
          performConcurrentWorkOnRoot.bind(null, root),
        );
      }
    }
    

三、协调阶段(Reconciliation)

在此阶段,React 遍历 Fiber 树,计算新状态并生成虚拟 DOM:

  1. 进入渲染流程

    // 源码位置:ReactFiberWorkLoop.js
    function performSyncWorkOnRoot(root) {
      renderRootSync(root, lanes); // 同步渲染根节点
      commitRoot(root); // 提交变更
    }
    
    function performConcurrentWorkOnRoot(root) {
      renderRootConcurrent(root, lanes); // 并发渲染根节点(可中断)
      commitRoot(root); // 提交变更
    }
    
  2. 处理更新队列(processUpdateQueue

    // 源码位置:ReactUpdateQueue.js
    function processUpdateQueue(workInProgress, props, instance, renderLanes) {
      const queue = workInProgress.updateQueue;
      let update = queue.shared.pending;
      queue.shared.pending = null; // 清空队列
    
      let newState = queue.baseState;
      while (update !== null) {
        const updateLane = update.lane;
        if (!isSubsetOfLanes(renderLanes, updateLane)) {
          // 跳过不满足优先级的更新
          continue;
        }
        // 调用 reducer 计算新状态
        const action = update.action;
        newState = queue.lastRenderedReducer(newState, action);
        update = update.next;
      }
      workInProgress.memoizedState = newState; // 更新 Fiber 节点的状态
    }
    
  3. 生成新虚拟 DOM
    在协调过程中,React 调用组件函数,生成新的子 Fiber 树:

    // 源码位置:ReactFiberBeginWork.js
    function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) {
      const nextChildren = renderWithHooks(
        current,
        workInProgress,
        Component,
        nextProps,
        context,
        renderLanes,
      );
      reconcileChildren(current, workInProgress, nextChildren, renderLanes);
    }
    

四、提交阶段(Commit)

将协调阶段计算的变更应用到真实 DOM,并执行副作用:

  1. 提交 DOM 变更

    // 源码位置:ReactFiberCommitWork.js
    function commitRootImpl(root, renderPriorityLevel) {
      commitMutationEffects(root, finishedWork); // 更新 DOM
      commitLayoutEffects(root, finishedWork);   // 执行 layout effects(如 useLayoutEffect)
      schedulePendingPassiveEffects();           // 调度 passive effects(如 useEffect)
    }
    
  2. 执行副作用(Effects)

    // 源码位置:ReactFiberCommitWork.js
    function commitLayoutEffects(finishedWork, root) {
      while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
        if (effectTag & Update) {
          // 执行 useLayoutEffect 的副作用
          commitHookEffectListMount(HookLayout | HookHasEffect, nextEffect);
        }
        nextEffect = nextEffect.nextEffect;
      }
    }
    

五、关键数据结构与函数

数据结构/函数作用
Fiber 节点存储组件状态、更新队列、子节点等信息,是协调过程的核心数据结构。
Update 对象描述一个状态更新(action + 优先级),通过链表组织成队列。
enqueueUpdate将更新对象加入队列,维护环形链表结构。
scheduleUpdateOnFiber触发调度,根据优先级选择同步或并发渲染模式。
processUpdateQueue遍历更新队列,调用 reducer 计算最新状态。

六、总结:useReducer 更新链路

  1. 触发更新dispatch(action) → 创建 Update 对象 → 加入队列 → 调度更新。
  2. 调度阶段:标记优先级 → 请求调度器执行任务(同步或并发模式)。
  3. 协调阶段:遍历 Fiber 树 → 处理更新队列 → 调用 reducer → 生成新虚拟 DOM。
  4. 提交阶段:更新 DOM → 执行副作用(useLayoutEffect/useEffect)。

核心特性

  • 优先级调度(Lane 模型):React 18+ 根据更新紧急程度(如用户输入、数据加载)动态调整执行顺序。
  • 批量更新(Batching):同一事件循环内的多个 dispatch 合并为一次渲染。
  • 可中断渲染(Concurrent Mode):高优先级任务可中断低优先级任务的渲染过程,提升用户体验。