react fiber

114 阅读6分钟

 什么是 React Fiber? 为什么要引入 React Fiber?

  1. fiber是 react16 以后引入的一种新的协调算法

旧的直接从vodm --> 真实DOM

新的从 vdom - fiber - 真实dom (真实DOM 存到 fiber节点的stateNode),真实DOM里面也可以通过internalInstanceKey取fiber

  1. 在旧的协调算法中react会从根组件开始渲染,并且这个渲染过程是不可以暂停的,如果有某些渲染任务会时间过长会占用主线程导致页面卡顿

  2. 所以引入了fiber,它是一种链表结构,会把渲染过程拆分成多个小的工作单元,并且每一个fiber节点会存储(return、child、sibling的指针),使渲染过程可以中断、暂停、恢复,避免,提高应用的响应性能,在每个任务完成后,会检查是否有更高优先级的任务需要处理

React Fiber 是如何工作的?

  1. 调和阶段(reconcile Phase)

    1.这个阶段是可中断的,React 会将整个渲染任务拆分成多个小的工作单元(Fiber 节点)

    2.按照深度优先的顺序遍历 Fiber 树,为每个 Fiber 节点执行 beginWork 和 completeWork 方法

    3.构建新的 Fiber 树(workInProgress 树)。在这个过程中,如果浏览器有更高优先级的任务需要处理,渲染可以被暂停

用while 循环执行 performUnitOfWork

beginWork作用:

  1. 调用renderWithHooks,处理hooks

    1. 有老fiber并且有老的hook链表fiber.memoizedState
    2. 否则:mountXXXHOOK初始化hooks链表,存到memoizedState,并且返回dispatch触发入队更新的函数
  2. 调用reconcileChildren,根据当前Fiber节点去构建子Fiber链表

    1. 首次渲染是构建fiber,第一个Fiber节点就是Root Fiber(存储着vdom)用于构建新的 Fiber 子链表,fiber pendingProps 存储的就是vdom props,每次取里面children构建子链表`

    2. 老Fiber的话,做DOM-DIFF 拿老的子fiber链表和新的子虚拟DOM进行比较 ,进行最小化的更新

completeWork作用:

  1. 根据 Fiber 节点创建真实 DOM,挂载所有子节点,将DOM存储到 stateNode 中

    更新阶段 diffProperties 对比新旧属性,生成更新队列

    标记副作用,合并子节点的副作用到 subtreeFlags

  1. 提交阶段(Commit Phase)

    1.此阶段是不可中断的,从alternate取出最新构建出的fiber树,执行副作用,修改真实 DOM

    2.检查并处理 subtreeFlags(子 fiber 的副作用)和自身的 flags,完成 DOM 的插入、更新等操作

    3.updateQueue 更新effect

    3.完成页面的更新。

遍历 workInProgress 树,根据节点的 flags 属性执行相应的副作用操作

什么是双缓存机制?

react 存在两棵 Fiber 树:current 树和 workInProgress 树

current 树:对应页面上当前显示的真实 DOM,代表已经渲染完成的 Fiber 树。

workInProgress 树: 正在构建中的新 Fiber 树,基于 current 树和新的虚拟 DOM 创建。在渲染阶段,React 会在 workInProgress 树上进行操作,当所有的更新完成后,workInProgress 树会变成新的

React Fiber 是如何实现可中断渲染的?

因为fiber是链表结构,中断后可以通过一个全局变量nextUnitOfWork存储fiber节点,并根据节点的childparentsibling 继续执行任务

如何实现时间分片

通过模拟浏览器的requestIdleCallbackapi,在浏览器空闲时段处理任务,每个工作单元执行后检查剩余时间,中断低优先级任务时,保存其进度,后续恢复或重新开始

不适用浏览器api原因:兼容差

react hooks 链表

函数组件

1.首次渲染时:React 会初始化一个链表来管理 Hooks。每个 Hook 都会被表示为链表中的一个节点,节点包含了 Hook 的状态、更新队列等信息。

2.渲染时:会根据链表结构遍历每个 Hook 节点,处理更新操作和副作用逻辑

react 怎么触发渲染 调度更新

举例:useState/useReducer的 setState | dispatch

如果setState触发更新则创建一个更新对象Update = { action, next, lane }

然后入队concurrentQueues = [fiber, queue, update, lane]这个并发队列的全局变量,再根据fiber 找到root fiber调用scheduleUpdateOnFiber构建新的root fiber树

并且从全局的并发队列(concurrentQueues)中依次取出每个更新任务,并将它们插入到对应 Fiber 节点的更新队列(UpdateQueue)中

export function finishQueueingConcurrentUpdates() {
  const endIndex = concurrentQueuesIndex;//9 只是一边界条件
  concurrentQueuesIndex = 0;
  let i = 0;
  while (i < endIndex) {
    const fiber = concurrentQueues[i++];
    const queue = concurrentQueues[i++];
    const update = concurrentQueues[i++];
    const lane = concurrentQueues[i++];
    if (queue !== null && update !== null) {
      const pending = queue.pending;
      if (pending === null) {
        update.next = update;
      } else {
        update.next = pending.next;
        pending.next = update;
      }
      queue.pending = update;
    }
  }
}

function enqueueUpdate(fiber, queue, update, lane) {
  //012 setNumber1 345 setNumber2 678 setNumber3
  concurrentQueues[concurrentQueuesIndex++] = fiber;//函数组件对应的fiber
  concurrentQueues[concurrentQueuesIndex++] = queue;//要更新的hook对应的更新队列
  concurrentQueues[concurrentQueuesIndex++] = update; //更新对象
  concurrentQueues[concurrentQueuesIndex++] = lane; //更新对应的赛道
  //当我们向一个fiber上添加一个更新的时候,要把此更新的赛道合并到此fiber的赛道上
  fiber.lanes = mergeLanes(fiber.lanes, lane);
}


/**
 * 渲染函数组件
 * @param {*} current 老fiber
 * @param {*} workInProgress 新fiber
 * @param {*} Component 组件定义
 * @param {*} props 组件属性
 * @returns 虚拟DOM或者说React元素
 */
export function renderWithHooks(current, workInProgress, Component, props, nextRenderLanes) {
  //当前正在渲染的车道
  renderLanes = nextRenderLanes
  currentlyRenderingFiber = workInProgress;
  //函数组件更新队列里存的effect
  workInProgress.updateQueue = null;
  //函数组件状态存的hooks的链表
  workInProgress.memoizedState = null;
  //如果有老的fiber,并且有老的hook链表
  if (current !== null && current.memoizedState !== null) {
    ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
  } else {
    ReactCurrentDispatcher.current = HooksDispatcherOnMount;
  }
  //需要要函数组件执行前给ReactCurrentDispatcher.current赋值
  const children = Component(props);
  currentlyRenderingFiber = null;
  workInProgressHook = null;
  currentHook = null;
  renderLanes = NoLanes;
  return children;
}

react useEffect

调用useEffect会创建一个effect对象,存储到fiber updateQueue,形成一个环形链表

const effect = {
  tag,        // 类型标记
  create,     // 副作用函数
  destroy,    // 清理函数
  deps,       // 依赖项数组
  next: null  // 指向下一个 Effect 的指针
};

fiber结构

// 一个典型的 Fiber 节点结构
const fiber = {
  // 基础信息
  tag: FunctionComponent,   // Fiber 类型(如 FunctionComponent、HostComponent、ClassComponent)
  key: 'list-item',         // 唯一标识(用于 Diffing 算法)
  type: MyComponent,        // 组件类型(函数/类组件本身,或 DOM 标签名如 'div')
  elementType: MyComponent, // 与 type 类似,但对某些高阶组件有差异
  stateNode: divElement,    // 对应的真实 DOM 节点(HostComponent)或组件实例(ClassComponent)

  // 树结构指针
  return: parentFiber,      // 父节点
  child: firstChildFiber,   // 第一个子节点
  sibling: nextSiblingFiber,// 下一个兄弟节点
  index: 0,                 // 在父节点的子节点中的位置

  // 副作用相关
  flags: Placement | Update,// 标记需要执行的副作用(如插入、更新、删除)
  subtreeFlags: NoFlags,    // 子树的副作用标记(优化遍历)
  deletions: [childFiber],  // 待删除的子节点(用于 Commit 阶段)
  updateQueue: {            // 更新队列(如状态更新、Effect)
    lastEffect: effect1,    // 环形链表尾指针(管理 useEffect、useLayoutEffect)
    stores: [],             // Context 相关存储
  },
  memoizedState: {          // Hooks 链表(useState、useEffect 等的状态)
    memoizedState: 'value', // 当前状态值
    next: nextHook,         // 下一个 Hook
    queue: {                // 状态更新队列(如 setState 的多次调用)
      pending: update1,     // 环形更新链表
    },
  },
  memoizedProps: { text: 'Hello' }, // 上次渲染的 Props
  pendingProps: { text: 'World' },  // 待处理的 Props(来自本次更新)

  // 调度相关
  lanes: DefaultLane,       // 当前 Fiber 的优先级(Lane 模型)
  childLanes: NoLanes,      // 子节点的优先级
  alternate: alternateFiber,// 双缓冲中的另一棵树节点(current/workInProgress)

  // 其他
  mode: ConcurrentMode,     // 渲染模式(ConcurrentMode、BlockingMode 等)
  ref: refObject,           // Ref 对象(如 createRef 的引用)
};