同事问我 Fiber 是什么,我直接甩给他这篇文章

1,194 阅读7分钟

在 react 16 版本中,官方对内部代码进行了大面积的重写,其中 fiber 就是最重要的一部分,那什么是 fiber 呢?

其实 fiber 就是一种新的 dom 比对的新的算法,fiber 就是这种算法的名字。在 react 16 版本之前,dom 对比的算法叫 stack(堆栈)。

那为什么 react 官方要重写 dom 的比对算法,而采用 filber 呢?

那是因为 stack 对更新虚拟 dom 的过程是采用循环加递归实现的,这种比对方式有一个问题,就是一旦任务开始进行就无法中断,如果应用中组件数量庞大,主线程被长期占用,直到整棵虚拟 dom 树比对更新完成之后主线程才能被释放,主线程才能执行其他任务。从而使应用无法及时响应用户的输入或其他高优先级任务,页面就会产生卡顿, 非常的影响用户体验。

核心问题:递归无法中断,执行重任务耗时长。 JavaScript 又是单线程,无法同时执行其他任务,导致任务延迟页面卡顿,用户体验差。

结构体

fiber 其实就是 javascript 对象,它是 Virtual DOM 对象演变而来的,在 fiber 对象有很多属性,这里我就挑出几个比较重要的属性放在了笔记中

type Fiber = {
  /************************  DOM 实例相关  *****************************/
  
  // 标记不同的组件类型, 值详见 WorkTag
  tag: WorkTag,

  // 组件类型 div、span、组件构造函数
  type: any,

  // 实例对象, 如类组件的实例、原生 dom 实例, 而 function 组件没有实例, 因此该属性是空
  stateNode: any,
 
	/************************  构建 Fiber 树相关  ***************************/
  
  // 指向自己的父级 Fiber 对象
  return: Fiber | null,

  // 指向自己的第一个子级 Fiber 对象
  child: Fiber | null,
  
  // 指向自己的下一个兄弟 iber 对象
  sibling: Fiber | null,
  
  // 在 Fiber 树更新的过程中,每个 Fiber 都会有一个跟其对应的 Fiber
  // 我们称他为 current <==> workInProgress
  // 在渲染完成之后他们会交换位置
  // alternate 指向当前 Fiber 在 workInProgress 树中的对应 Fiber
	alternate: Fiber | null,
		
  /************************  状态数据相关  ********************************/
  
  // 即将更新的 props
  pendingProps: any, 
  // 旧的 props
  memoizedProps: any,
  // 旧的 state
  memoizedState: any,
		
  /************************  副作用相关 ******************************/

  // 该 Fiber 对应的组件产生的状态更新会存放在这个队列里面 
  updateQueue: UpdateQueue<any> | null,
  
  // 用来记录当前 Fiber 要执行的 DOM 操作
  effectTag: SideEffectTag,

  // 存储第一个要执行副作用的子级 Fiber 对象
  firstEffect: Fiber | null,
  
  // 存储下一个要执行副作用的子级 Fiber 对象
  // 执行 DOM 渲染时要先通过 first 找到第一个, 然后通过 next 一直向后查找
  nextEffect: Fiber | null,
  
  // 存储 DOM 操作完后的副作用 比如调用生命周期函数或者钩子函数的调用
  lastEffect: Fiber | null,

  // 任务的过期时间
  expirationTime: ExpirationTime,
  
	// 当前组件及子组件处于何种渲染模式 详见 TypeOfMode
  mode: TypeOfMode,
};
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

export type WorkTag =
  | 0
  | 1
  | 2
  | 3
  | 4
  | 5
  | 6
  | 7
  | 8
  | 9
  | 10
  | 11
  | 12
  | 13
  | 14
  | 15
  | 16
  | 17
  | 18
  | 19
  | 20
  | 21
  | 22
  | 23
  | 24
  | 25
  | 26
  | 27;

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;
export const TracingMarkerComponent = 25;
export const HostHoistable = 26;
export const HostSingleton = 27;
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

export type TypeOfMode = number;

// 同步渲染模式
export const NoMode = /*                         */ 0b0000000;
// TODO: Remove ConcurrentMode by reading from the root tag instead
// 异步渲染模式
export const ConcurrentMode = /*                 */ 0b0000001;
// 性能测试模式
export const ProfileMode = /*                    */ 0b0000010;
export const DebugTracingMode = /*               */ 0b0000100;
export const StrictLegacyMode = /*               */ 0b0001000;
export const StrictEffectsMode = /*              */ 0b0010000;
export const ConcurrentUpdatesByDefaultMode = /* */ 0b0100000;
export const NoStrictPassiveEffectsMode = /*     */ 0b1000000;

双缓存

如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。

为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,这样的话在帧画面替换的过程中就会节约非常多的时间,就不会出现白屏问题。这种在内存中构建并直接替换的技术叫做双缓存

React 使用双缓存技术完成 Fiber 树的构建与替换,实现DOM对象的快速更新。

在 React 中最多会同时存在两棵 Fiber 树,当前在屏幕中显示的内容对应的 Fiber 树叫做 current Fiber 树,当发生更新时,React 会在内存中重新构建一颗新的 Fiber 树,这颗正在构建的 Fiber 树叫做 workInProgress Fiber 树。

在双缓存技术中,workInProgress Fiber 树就是即将要显示在页面中的 Fiber 树,当这颗 Fiber 树构建完成后,React 会使用它直接替换 current Fiber 树达到快速更新 DOM 的目的,因为 workInProgress Fiber 树是在内存中构建的所以构建它的速度是非常快的。

一旦 workInProgress Fiber 树在屏幕上呈现,它就会变成 current Fiber 树。

双缓存树一个显著的特点就是两棵树之间会互相切换,通过alternate属性连接

currentFiber.alternate === workInProgressFiber;

workInProgressFiber.alternate === currentFiber;

构建 Fiber 树的过程

在调用 createRoot(并发模式)或者 ReactDOM.render(同步模式)时,会执行 createFiberRoot 方法。

export function createFiberRoot(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  initialChildren: ReactNodeList,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  // TODO: We have several of these arguments that are conceptually part of the
  // host config, but because they are passed in at runtime, we have to thread
  // them through the root constructor. Perhaps we should put them all into a
  // single type, like a DynamicHostConfig that is defined by the renderer.
  identifierPrefix: string,
  onRecoverableError: null | ((error: mixed) => void),
  transitionCallbacks: null | TransitionTracingCallbacks,
  formState: ReactFormState<any, any> | null,
): FiberRoot {
  
  // 生成 fiberRoot
  const root: FiberRoot = new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError,
    formState,
  )
 
  // ...

  // 生成 rootFiber
  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );

  // 互相关联
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  // .....

  return root;
}

createFiberRoot 创建一个了 fiberRoot,以及一个 rootFiber。它们的关系如下:

在 React 中 fiberRoot 只有一个,而 rootFiber 可以有多个,因为 render 方法是可以调用多次的 fiberRoot 会记录应用的更新信息,比如协调器在完成工作后,会将工作成果存储在 fiberRoot 中。

工作流程

render

/**
 * 渲染入口
 * element 要进行渲染的 ReactElement, createElement 方法的返回值
 * container 渲染容器 <div id="root"></div>
 * callback 渲染完成后执行的回调函数
 */
export function render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
): React$Component<any, any> | PublicInstance | null {
  // 检测 container 是否是符合要求的渲染容器
  // 即检测 container 是否是真实的DOM对象
  // 如果不符合要求就报错
  if (!isValidContainerLegacy(container)) {
    throw new Error('Target container is not a DOM element.');
  }

  return legacyRenderSubtreeIntoContainer(
    // 父组件 初始渲染没有父组件 传递 null 占位
    null,
    element,
    container,
    // 是否为服务器端渲染 false 不是服务器端渲染 true 是服务器端渲染
    false,
    callback,
  );
}

isValidContainerLegacy

/**
 * 判断 node 是否是符合要求的 DOM 节点
 * 1. node 可以是元素节点
 * 2. node 可以是 document 节点
 * 3. node 可以是 文档碎片节点
 * 4. node 可以是注释节点但注释内容必须是 react-mount-point-unstable
 * 		react 内部会找到注释节点的父级 通过调用父级元素的 insertBefore 方法, 将 element 插入到注释节点的前面
 */
export function isValidContainerLegacy(node: any): boolean {
  return !!(
    node &&
    (node.nodeType === ELEMENT_NODE ||
      node.nodeType === DOCUMENT_NODE ||
      node.nodeType === DOCUMENT_FRAGMENT_NODE ||
      (node.nodeType === COMMENT_NODE &&
        (node: any).nodeValue === ' react-mount-point-unstable '))
  );
}

legacyRenderSubtreeIntoContainer

/**
 * 将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)
 * parentComponent: 父组件, 初始渲染传入了 null
 * children: render 方法中的第一个参数, 要渲染的 ReactElement
 * container: 渲染容器
 * forceHydrate: true 为服务端渲染, false 为客户端渲染
 * callback: 组件渲染完成后需要执行的回调函数
 **/
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
): React$Component<any, any> | PublicInstance | null {
  /**
   * 检测 container 是否已经是初始化过的渲染容器
   * react 在初始渲染时会为最外层容器添加 _reactRootContainer 属性
   * react 会根据此属性进行不同的渲染方式
   * maybeRoot 不存在 表示初始渲染
   * maybeRoot 存在 表示更新
   */
  // 获取 container 容器对象下是否有 _reactRootContainer 属性
  const maybeRoot = container._reactRootContainer;

  // 即将存储根 Fiber 对象
  let root: FiberRoot;

  if (!maybeRoot) {
    // 初始渲染
    // 初始化根 Fiber 数据结构
    root = legacyCreateRootFromDOMContainer(
      container,
      children,
      parentComponent,
      callback,
      forceHydrate,
    );
  } else {
    // 非初始化渲染 即更新
    // ...
  }

  // 返回 render 方法第一个参数的真实 DOM 对象作为 render 方法的返回值
  // 就是说渲染谁 返回谁的真实 DOM 对象
  return getPublicRootInstance(root);
}


function legacyCreateRootFromDOMContainer(
  container: Container,
  initialChildren: ReactNodeList,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
  isHydrationContainer: boolean,
): FiberRoot {
  if (isHydrationContainer) {
    // 服务端渲染
    // ...
  } 

  // 客户端渲染
  // First clear any existing content.
  clearContainer(container);

  /**
   * 改变 callback 函数中的 this 指向
   * 使其指向 render 方法第一个参数的真实 DOM 对象
   */
  // 如果 callback 参数是函数类型
  if (typeof callback === 'function') {
    const originalCallback = callback;
    callback = function () {
      const instance = getPublicRootInstance(root);
      originalCallback.call(instance);
    };
  }

  // 返回项目中只有唯一一个的 FiberRoot 节点
  const root = createContainer(
    container,
    LegacyRoot,
    null, // hydrationCallbacks
    false, // isStrictMode
    false, // concurrentUpdatesByDefaultOverride,
    '', // identifierPrefix
    noopOnRecoverableError, // onRecoverableError
    null, // transitionCallbacks
  );

  // 对 container._reactRootContainer 重新赋值,
  // 以后在调用 legacyRenderSubtreeIntoContainer 方法时不会再再进入创建方法中
  container._reactRootContainer = root;

  // 将DOM节点 container 标记为已被作为root使用过
  // 并通过一个属性指向到fiber节点:
  // container['__reactContainer$'] = root.current; // root为fiber类型的节点
  // 这里就形成了互相指向,root.containerInfo = container;
  markContainerAsRoot(root.current, container);

  // 获取container的真实element元素,若container是注释类型的元素,则使用其父级元素,否则直接使用container
  // 大概是因为注释节点无法挂载事件
  const rootContainerElement =
    container.nodeType === COMMENT_NODE ? container.parentNode : container;
  
  // 绑定所有可支持的事件到 rootContainerElement 节点上
  listenToAllSupportedEvents(rootContainerElement);

  // Initial mount should not be batched.
  // 让 updateContainer 的更新变成同步,执行完 updateContainer 后再返回 root
  flushSync(() => {
    updateContainer(initialChildren, root, parentComponent, callback);
  });

  return root;
}
export function clearContainer(container: Container): void {
  const nodeType = container.nodeType;
  if (nodeType === DOCUMENT_NODE) {
    clearContainerSparingly(container);
  } else if (nodeType === ELEMENT_NODE) {
    switch (container.nodeName) {
      case 'HEAD':
      case 'HTML':
      case 'BODY':
        clearContainerSparingly(container);
        return;
      default: {
        container.textContent = '';
      }
    }
  }
}

function clearContainerSparingly(container: Node) {
  let node;
  let nextNode: ?Node = container.firstChild;
  if (nextNode && nextNode.nodeType === DOCUMENT_TYPE_NODE) {
    nextNode = nextNode.nextSibling;
  }
  
  while (nextNode) {
    node = nextNode;
    nextNode = nextNode.nextSibling;
    switch (node.nodeName) {
      case 'HTML':
      case 'HEAD':
      case 'BODY': {
        const element: Element = (node: any);
        clearContainerSparingly(element);
        
        // 删除相关字段
        detachDeletedInstance(element);
        continue;
      }
      // Script tags are retained to avoid an edge case bug. Normally scripts will execute if they
      // are ever inserted into the DOM. However when streaming if a script tag is opened but not
      // yet closed some browsers create and insert the script DOM Node but the script cannot execute
      // yet until the closing tag is parsed. If something causes React to call clearContainer while
      // this DOM node is in the document but not yet executable the DOM node will be removed from the
      // document and when the script closing tag comes in the script will not end up running. This seems
      // to happen in Chrome/Firefox but not Safari at the moment though this is not necessarily specified
      // behavior so it could change in future versions of browsers. While leaving all scripts is broader
      // than strictly necessary this is the least amount of additional code to avoid this breaking
      // edge case.
      //
      // Style tags are retained because they may likely come from 3rd party scripts and extensions
      case 'SCRIPT':
      case 'STYLE': {
        continue;
      }
      // Stylesheet tags are retained because tehy may likely come from 3rd party scripts and extensions
      case 'LINK': {
        if (((node: any): HTMLLinkElement).rel.toLowerCase() === 'stylesheet') {
          continue;
        }
      }
    }
    container.removeChild(node);
  }
  return;
}

Q: 为什么首次 render 时需要判断 maybeRoot?

A: legacyRenderSubtreeIntoContainer 除了在 render 方法里面调用还在其他方法中有调用。其他方法在调用时,root 可能已经存在,所以在调用时,需要先判断一下 container._reactRootContainer 是否存在。如果存在,就应该走 update 路线。

createContainer

export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onRecoverableError: (error: mixed) => void,
  transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
  const hydrate = false;
  const initialChildren = null;
  
  return createFiberRoot(
    containerInfo,
    tag,
    hydrate,
    initialChildren,
    hydrationCallbacks,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
    null,
  );
}

可以看到 createContainer 只做了一件事,返回执行 createFiberRoot 方法后的值,这个值就是一个项目中只有唯一一个的 FiberRoot 节点。

createFiberRoot

/*
  containerInfo: 容器信息,表示要在其中创建 Fiber Root 的容器。
  tag: 根节点的标签,通常是 RootTag 中的一个值,用于区分不同类型的根节点。
  hydrate: 是否进行混合(Hydrate)操作,通常在服务端渲染(SSR)时会用到。
  initialChildren: 初始子节点,通常是要在 Fiber Root 中渲染的 React 元素。
  hydrationCallbacks: 混合操作的回调函数,用于在混合操作过程中执行一些逻辑。
  isStrictMode: 是否启用严格模式。
  concurrentUpdatesByDefaultOverride: 是否默认启用并发更新。
  identifierPrefix: 标识符前缀,用于标识该 Fiber Root。
  onRecoverableError: 可恢复错误的回调函数,用于处理在渲染过程中发生的错误。
  transitionCallbacks: 过渡追踪的回调函数,用于追踪过渡动画的执行。
  formState: 表单状态,用于管理 React 表单组件的状态。
*/

export function createFiberRoot(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  initialChildren: ReactNodeList,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  // TODO: We have several of these arguments that are conceptually part of the
  // host config, but because they are passed in at runtime, we have to thread
  // them through the root constructor. Perhaps we should put them all into a
  // single type, like a DynamicHostConfig that is defined by the renderer.
  identifierPrefix: string,
  onRecoverableError: null | ((error: mixed) => void),
  transitionCallbacks: null | TransitionTracingCallbacks,
  formState: ReactFormState<any, any> | null,
): FiberRoot {
  // 创建 fiberRoot 根节点,在 react 项目中 fiberRoot 根节点只存在一个
  const root: FiberRoot = (new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError,
    formState,
  ): any);
  
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  if (enableTransitionTracing) {
    root.transitionCallbacks = transitionCallbacks;
  }

  // 创建 rootFiber,可以是多个
  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );
  
  // 用 current 将 FiberRoot 和 RootFiber 相关联
  root.current = uninitializedFiber;
  // 使用 stateNode 关联了 FiberRoot 实例。
  // rootFiber 可以看做是 render 内容的根节点
  uninitializedFiber.stateNode = root;

  // ...

  // 为 fiber 对象添加 updateQueue 属性, 初始化 updateQueue 对象
  // updateQueue 用于存放 Update 对象
  // Update 对象用于记录组件状态的改变
  initializeUpdateQueue(uninitializedFiber);

  return root;
}

在 createFiberRoot 里面,创建了一个 FiberRoot 并且还创建了一个 rootFiber。并将它们通过 current 和 stateNode 进行相互关联。这也就实现了最初我们看到的图。

在创建完成 fiberRoot 和 rootFiber 之后会

export function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
      lanes: NoLanes, // 0b0000000000000000000000000000000
      hiddenCallbacks: null,
    },
    callbacks: null,
  };
  fiber.updateQueue = queue;
}

当 FiberRoot 创建好后,就会执行 updateContainer 方法:

function legacyCreateRootFromDOMContainer(
  container: Container,
  initialChildren: ReactNodeList,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
  isHydrationContainer: boolean,
): FiberRoot {
  // ...
  
  // 让 updateContainer 的更新变成同步,执行完 updateContainer 后再返回 root
  flushSync(() => {
    updateContainer(initialChildren, root, parentComponent, callback);
  });

  return root;
}

updateContainer

/**
 * 计算任务的过期时间
 * 再根据任务过期时间创建 Update 任务
 */
export function updateContainer(
  // element 要渲染的 ReactElement
  element: ReactNodeList,
  // container Fiber Root 对象
  container: OpaqueRoot,
  // parentComponent 父组件 初始渲染为 null
  parentComponent: ?React$Component<any, any>,
  // ReactElement 渲染完成执行的回调函数
  callback: ?Function,
): Lane {
  // 获取 rootFiber
  const current = container.current;
  const lane = requestUpdateLane(current);

  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }

  // 设置FiberRoot.context, 首次执行返回一个emptyContext, 是一个 {}
  const context = getContextForSubtree(parentComponent);

  // 初始渲染时 Fiber Root 对象中的 context 属性值为 null
  // 所以会进入到 if 中
  if (container.context === null) {
    // 初始渲染时将 context 属性值设置为 {}
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  // 创建一个待执行任务
  const update = createUpdate(lane);
  // 将要更新的内容挂载到更新对象中的 payload 中
  // 将要更新的组件存储在 payload 对象中, 方便后期获取
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    // 将 callback 挂载到 update 对象中
    // 其实就是一层层传递 方便 ReactElement 元素渲染完成调用
    // 回调函数执行完成后会被清除 可以在代码的后面加上 return 进行验证
    update.callback = callback;
  }


  /*
    下面这段代码总结起来就是将更新对象添加到当前 Fiber 节点的更新队列中,
    并在必要时调度更新,同时处理更新的过渡动画。
  */

  // 将更新对象添加到当前 Fiber 节点的更新队列中, 返回一个新的根 Fiber 节点
  const root = enqueueUpdate(current, update, lane);

  // 如果不为 null,则成功添加更新对象到更新队列中
  if (root !== null) {
    // 调度和更新 current 对象
    scheduleUpdateOnFiber(root, current, lane);
    // 处理更新的过渡,确保更新过程中的过渡动画能够正确地执行
    entangleTransitions(root, current, lane);
  }

  return lane;
}
  • 请求当前 Fiber 节点的 lane(优先级);
  • 结合 lane(优先级),创建当前 Fiber 节点的 update 对象,并将其入队;
  • 调度当前节点(rootFiber);

通过 createUpate 创建一个更新。调用 enqueueUpdate 将这个更新加入到更新队列中。然后执行 scheduleUpdateOnFiber 开始调度。

scheduleUpdateOnFiber

export function scheduleUpdateOnFiber(
  root: FiberRoot, // 根节点容器
  fiber: Fiber, // 当前Fiber节点
  lane: Lane, // 更新优先级
) {
  // ...
  
  // 将lane合并到root.pendingLanes上。root.pendingLanes |= lane; 
  markRootUpdated(root, lane);

  if (
    (executionContext & RenderContext) !== NoLanes &&
    root === workInProgressRoot
  ) {
    // ...
  } else {
    // ...
    ensureRootIsScheduled(root, eventTime);
    // ...
  }
}
export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
  // 设置本次更新的优先级
  root.pendingLanes |= updateLane;
  
  // 如果updateLane优先级不是为空闲级的优先级【不是低优先级,当前不是】
  if (updateLane !== IdleLane) {
    root.suspendedLanes = NoLanes;
    root.pingedLanes = NoLanes;
  }
}

其中 ensureRootIsScheduled 是更新的必经之路,负责不同优先级任务的调度,并产生调度优先级

let firstScheduledRoot: FiberRoot | null = null;
let lastScheduledRoot: FiberRoot | null = null;

let mightHavePendingSyncWork: boolean = false;
let didScheduleMicrotask: boolean = false;

let currentEventTransitionLane: Lane = NoLane;

export function ensureRootIsScheduled(root: FiberRoot): void {
  if (root === lastScheduledRoot || root.next !== null) {
    // Fast path. This root is already scheduled.
  } else {
    if (lastScheduledRoot === null) {
      firstScheduledRoot = lastScheduledRoot = root;
    } else {
      lastScheduledRoot.next = root;
      lastScheduledRoot = root;
    }
  }
  
  mightHavePendingSyncWork = true;

  if (__DEV__ && ReactCurrentActQueue.current !== null) {
    // ...
  } else {
    // 模块内全局变量,初始false
    if (!didScheduleMicrotask) {
      didScheduleMicrotask = true;
      scheduleImmediateTask(processRootScheduleInMicrotask);
    }
  }
  // ...
}

function processRootScheduleInMicrotask() {
  didScheduleMicrotask = false;
  mightHavePendingSyncWork = false;

  // ...

  // 批量更新所有同步任务
  flushSyncWorkOnAllRoots();
}

export function flushSyncWorkOnAllRoots() {
  flushSyncWorkAcrossRoots_impl(false);
}

function flushSyncWorkAcrossRoots_impl(onlyLegacy: boolean) {
  // 否正在执行工作
  if (isFlushingWork) {
    return;
  }

  // 是否存在待处理的同步工作
  if (!mightHavePendingSyncWork) {
    return;
  }

  // There may or may not be synchronous work scheduled. Let's check.
  let didPerformSomeWork;
  let errors: Array<mixed> | null = null;
  isFlushingWork = true;

  // 执行每个根节点上的同步工作
  do {
    didPerformSomeWork = false;
    let root = firstScheduledRoot;
    
    while (root !== null) {
      if (onlyLegacy && root.tag !== LegacyRoot) {
        // Skip non-legacy roots.
      } else {
        const workInProgressRoot = getWorkInProgressRoot();

        // 获取渲染优先级
        const workInProgressRootRenderLanes =
          getWorkInProgressRootRenderLanes();

        // 下一个待处理的渲染优先级
        const nextLanes = getNextLanes(
          root,
          root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
        );

        // 是否存在同步优先级 !== NoLanes
        if (includesSyncLane(nextLanes)) {
          // 渲染优先级中包含同步优先级
          try {
            didPerformSomeWork = true;

            // 同步任务的入口点,用于在根 Fiber 上执行同步工作
            performSyncWorkOnRoot(root, nextLanes);
          } catch (error) {
            // 执行同步工作期间抛出了错误,则将错误收集起来
            if (errors === null) {
              errors = [error];
            } else {
              errors.push(error);
            }
          }
        }
      }
      root = root.next;
    }
  } while (didPerformSomeWork);
  isFlushingWork = false;

  // 捕获同步工作过程中可能抛出的多个错误,并根据环境的支持情况进行合适的处理
  // ...
}

开始调度,调用 performSyncWorkOnRoot 方法。

export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null {
  // ...

  // 同步渲染根 Fiber 树,返回渲染的退出状态
  let exitStatus = renderRootSync(root, lanes);

  // ...

  const finishedWork: Fiber = (root.current.alternate: any);
  
  // 将构建好的新 Fiber 对象存储在 finishedWork 属性中
  // 提交阶段使用
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;

  // 结束 render 阶段
  // 进入 commit 阶段
  commitRoot(
    root,
    workInProgressRootRecoverableErrors,
    workInProgressTransitions,
    workInProgressDeferredLane,
  );

  ensureRootIsScheduled(root);

  return null;
}

这个方法中先执行 renderRootSync 执行完成后,再执行 commitRoot

renderRootSync

function renderRootSync(root: FiberRoot, lanes: Lanes) {
  // ...
  
  // 如果 root 和 workInProgressRoot 不相等
  // 说明 workInProgressRoot 不存在, 说明还没有构建 workInProgress Fiber 树
  // workInProgressRoot 为全局变量 默认值为 null, 初始渲染时值为 null
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    // ...
    
    workInProgressTransitions = getTransitionsForLanes(root, lanes);
    // 创建一个 workInProgress 树
    prepareFreshStack(root, lanes);
  }

  if (enableSchedulingProfiler) {
    markRenderStarted(lanes);
  }

  let didSuspendInShell = false;
  do {
    try {
      // ...
      // 以同步的方式开始构建 Fiber 对象
      workLoopSync();

      // 跳出 while 循环
      break;
    } catch (thrownValue) {
      handleThrow(root, thrownValue);
    }
  } while (true);

  if (didSuspendInShell) {
    root.shellSuspendCounter++;
  }

  resetContextDependencies();

  executionContext = prevExecutionContext;
  popDispatcher(prevDispatcher);
  popCacheDispatcher(prevCacheDispatcher);

  if (workInProgress !== null) {
    // 这是一个同步渲染, 所以我们应该完成整棵树.
    // 无法提交不完整的 root, 此错误可能是由于React中的错误所致. 请提出问题.
    throw new Error(
      'Cannot commit an incomplete root. This error is likely caused by a ' +
        'bug in React. Please file an issue.',
    );
  }
  
  if (enableSchedulingProfiler) {
    markRenderStopped();
  }

  workInProgressRoot = null;
  workInProgressRootRenderLanes = NoLanes;

  finishQueueingConcurrentUpdates();

  // 返回渲染的结果状态
  return workInProgressRootExitStatus;
}

在这个方法中,调用 prepareFreshStack 方法创建了一个 workInProgress 树

prepareFreshStack

/**
 * 根据 currentFiber 树中的 rootFiber
 * 构建 workInProgressFiber 树中的 rootFiber
 */
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
  // 为 FiberRoot 对象添加 finishedWork 属性
  // finishedWork 表示 render 阶段执行完成后构建的待提交的 Fiber 对象
  root.finishedWork = null;
  // 初始化优先级
  root.finishedLanes = NoLanes;

  // ...

  
  // 建构 workInProgress Fiber 树的 Fiber 对象
  workInProgressRoot = root;
  // 构建 workInProgress Fiber 树中的 rootFiber
  const rootWorkInProgress = createWorkInProgress(root.current, null);
  
  // ...

  // 清空异步更新队列
  finishQueueingConcurrentUpdates();

  return rootWorkInProgress;
}

在 ReactFiberWorkLoop.js 中,workInProgress 是一个全局变量,所以这里通过 createWorkInProgress 方法将 currentFiber 复制成一个新的 workInProgress 树,后面的操作也都将是在这个 workInProgress 树上进行。

workLoopSync

通过 prepareFreshStack 创建一个 workInProgress 树后,我们需要开始循环调用 workLoopSync

// 以同步的方式构建 workInProgress Fiber 对象
function workLoopSync() {
  // workInProgress 是一个 fiber 对象
  // 它的值不为 null 意味着该 fiber 对象上仍然有更新要执行
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

通过对 workInProgress 是否为 null 来决定是否退出循环,通过对 JSX 从上向下进行遍历,然后生成的一棵完整的 Fiber 树

performUnitOfWork:workInProgress fiber会和已经创建的Fiber连接起来形成Fiber树。这个过程为深度优先遍历,分为 两个阶段

  • 递阶段:从根节点rootFiber开始,遍历到叶子节点,每次遍历到的节点都会执行beginWork,并且传入当前Fiber节点,然后创建或复用它的子Fiber节点,并赋值给workInProgress.child。
  • 归阶段:在归阶段遍历到子节点之后,会执行completeWork方法,执行完成之后会判断此节点的兄弟节点存不存在,如果存在就会为兄弟节点执行completeWork,当全部兄弟节点执行完之后,会向上冒泡到父节点执行completeWork,直到rootFiber。

首次进来的 workInProgress 因为是由 currentFiber 复制得来,所以此时的 workInProgress 指向的是 FiberRoot 节点。

performUnitOfWork

function performUnitOfWork(unitOfWork: Fiber): void {
  // unitOfWork => workInProgress Fiber 树中的 rootFiber
  // current => currentFiber 树中的 rootFiber
  const current = unitOfWork.alternate;

  // 存储下一个要构建的子级 Fiber 对象
  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
     // 初始渲染 不执行
    startProfilerTimer(unitOfWork);
    
    // beginWork的主要工作,则是从root fiber开始向下深度遍历构建workInprogress树,进行diff算法确定需要更新的fiber的最终状态。
    next = beginWork(current, unitOfWork, entangledRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    // beginWork: 从父到子, 构建 Fiber 节点对象
    // 返回值 next 为当前节点的子节点
    next = beginWork(current, unitOfWork, entangledRenderLanes);
  }

  // 为旧的 props 属性赋值
  // 此次更新后 pendingProps 变为 memoizedProps
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  // 如果子节点不存在说明当前节点向下遍历子节点已经到底了
  // 继续向上返回 遇到兄弟节点 构建兄弟节点的子 Fiber 对象 直到返回到根 Fiber 对象
  if (next === null) { 
    // 从子到父, 构建其余节点 Fiber 对象
    completeUnitOfWork(unitOfWork);
  } else {
    // 重新赋值 workInProgress
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}

FiberRoot 节点从这里开始进行 beginWork 遍历。如果能找到它的下一级,就继续进行 beginWork 操作。当下一级是文本节点时,就进行 completeUnitOfWork 操作。

beginWork

import { beginWork as originBeginWork } from './ReactFiberBeginWork.js'

let beginWork;
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
  // ...
} else {
  beginWork = originalBeginWork;
}

我们可以看到 beginWork 就是 originBeginWork 得实际执行。我们翻开 beginWork 的源码可以看到,它便是根据不同的 workInProgress.tag 执行不同组件类型的处理函数

// 从父到子, 构建 Fiber 节点对象
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  /**
   * 这里的我们可以不用太关注,只需关注下面的 switch
   */
  
  //  current 节点不为空的情况下,会加一道辨识,看看是否有更新逻辑要处理
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    // 若 props 更新或者上下文改变,则认为需要"接受更新"
    if (
      oldProps !== newProps ||
      hasLegacyContextChanged()
    ) {
      // 打更新标记
      didReceiveUpdate = true;
    } else {
      // 处理其他种情况,可忽略
      // ...
    }
  } else {
    didReceiveUpdate = false;

    if (getIsHydrating() && isForkedChild(workInProgress)) {
      const slotIndex = workInProgress.indee;
      const numberOfForks = getForksAtLevel(workInProgress);
      pushTreeId(workInProgress, numberOfForks, slotIndex);
    }
  }

  workInProgress.lanes = NoLanes;

  // switch 是 beginWork 中的核心逻辑,
  // 根据tag来创建不同的fiber 最后进入reconcileChildren函数
  switch (workInProgress.tag) {
    // ...
    // 函数型组件在第一次渲染组件时使用
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current, // 旧 Fiber
        workInProgress, // 新 Fiber
        workInProgress.type, // 新 Fiber 的 type 值 初始渲染时是App组件函数
        renderLanes, // 渲染优先级
      );
    }
    // 根节点将进入这个逻辑
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    // dom 标签对应的节点将进入这个逻辑
   case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    // 文本节点将进入这个逻辑
    case HostText:
      return updateHostText(current, workInProgress);

    // ...
  }

  // 处理 switch 匹配不上的情况
  throw new Error(
    `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
      'React. Please file an issue.',
  );
}

首次渲染时除了 rootFiber 外,current 等于 null,因为首次渲染 dom 还没构建出来,在 update 时 current 不等于 null,因为 update 时 dom 树已经存在了,beginWork 函数中用 current !== null 来判断是 update 还是mount 来分别进入不同的处理逻辑。

  • mount:根据 fiber.tag 进入不同fiber的创建函数,最后都会调用到 reconcileChildren 创建子 Fiber。
  • update:在构建 workInProgress 的时候,当满足条件时,会复用 currentFiber 来进行优化,也就是进入bailoutOnAlreadyFinishedWork的逻辑,didReceiveUpdate = false 可以直接复用。

初次渲染首先会执行 updateHostRoot,当后续 workInProgress 更新 ,则会执行 mountIndeterminateComponent,如果 APP 的子级为 div 等 element 时,则再去执行updateHostComponent

这里需要注意下两者的区别:它们最终都会执行reconcileChildren(),并返回workInProgress.child,不同的是children从何处获取

// HostRoot => <div id="root"></div> 对应的 Fiber 对象
// 找出 HostRoot 的子 ReactElement 并为其构建 Fiber 对象
function updateHostRoot(
  current: null | Fiber,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  pushHostRootContext(workInProgress);

  if (current === null) {
    throw new Error('Should have a current fiber. This is a bug in React.');
  }
  
  // 获取新的 props 对象
  const nextProps = workInProgress.pendingProps;
  // 获取上一次渲染使用的 state
  const prevState = workInProgress.memoizedState;
  // 获取上一次渲染使用的 children
  const prevChildren = prevState.element;
  // 浅复制更新队列, 防止引用属性互相影响
  // workInProgress.updateQueue 浅拷贝 current.updateQueue
  cloneUpdateQueue(current, workInProgress);
  // 获取 updateQueue.payload 并赋值到 workInProgress.memoizedState
  // 要更新的内容就是 element 就是 rootFiber 的子元素
  processUpdateQueue(workInProgress, nextProps, null, renderLanes);

  // 获取 element 所在对象
  const nextState: RootState = workInProgress.memoizedState;
  // 获取 fiberRoot 对象
  const root: FiberRoot = workInProgress.stateNode;
  pushRootTransition(workInProgress, root, renderLanes);

  if (enableTransitionTracing) {
    pushRootMarkerInstance(workInProgress);
  }

  if (enableCache) {
    // ...
  }

  suspendIfUpdateReadFromEntangledAsyncAction();

  const nextChildren = nextState.element;
  if (supportsHydration && prevState.isDehydrated) {
    // 服务队渲染
    // ...
  } else {
    resetHydrationState();

    // 下一个子节点与先前的子节点相同
    if (nextChildren === prevChildren) {
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
    
    // 构建子节点 fiber 对象
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  }
  
  // 返回子节点 fiber 对象
  return workInProgress.child;
}

reconcileChildren

switch (workInProgress.tag) 中处理处理函数都会通过调用 reconcileChildren 方法,生成当前节点的子节点,然后继续深度优先遍历它的子节点执行相同的操作。

export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  // current === null,说明当前正在处理的组件是全新的,之前没有被渲染过
  if (current === null) {
    // mount
    
    // 调用 mountChildFibers 函数根据子元素创建 Fiber,并赋值给 workInProgress.child
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else {
    // update
    
    // 对比新旧节点,然后进行增删改等操作
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}

从源码来看,reconcileChildren 也只是做逻辑的分发,具体的工作还要到 mountChildFibers 和 reconcileChildFibers 里去看。

mountChildFibers / reconcileChildFibers

export const reconcileChildFibers: ChildReconciler = createChildReconciler(true);
export const mountChildFibers: ChildReconciler = createChildReconciler(false);

reconcileChildren 会根据 current === null 条件来进入 mountChildFibers 或 reconcileChildren 函数中。它们会调用 ChildReconciler 函数

function createChildReconciler(
  shouldTrackSideEffects: boolean,
): ChildReconciler {
  // ...

  function reconcileChildFibersImpl(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
  ): Fiber | null {
    // 判断新的子 vdom 是否为占位组件 比如 <></>
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;

    // 如果 newChild 为占位符, 使用 占位符组件的子元素作为 newChild
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    }

    // 检测 newChild 是否为对象类型
    if (typeof newChild === 'object' && newChild !== null) {
      // 匹配子元素的类型
      switch (newChild.$$typeof) {
        // 子元素为 ReactElement
        case REACT_ELEMENT_TYPE:
          // 为 Fiber 对象设置 effectTag 属性
          // 返回创建好的子 Fiber
          return placeSingleChild(
            // 处理单个 React Element 的情况
            // 内部会调用其他方法创建对应的 Fiber 对象
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
        // ...
      }
      
      // children 是数组的情况
      if (isArray(newChild)) {
        // 返回创建好的子 Fiber
        return reconcileChildrenArray(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        );
      }

      // 是否具有迭代器函数
      if (getIteratorFn(newChild)) {
        return reconcileChildrenIterator(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        );
      }

      // 是否是 Promise 对象,
      if (typeof newChild.then === 'function') {
        const thenable: Thenable<any> = (newChild: any);
        return reconcileChildFibersImpl(
          returnFiber,
          currentFirstChild,
          // 调用 unwrapThenable 函数将其解包
          unwrapThenable(thenable),
          lanes,
        );
      }

      if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
        const context: ReactContext<mixed> = (newChild: any);
        return reconcileChildFibersImpl(
          returnFiber,
          currentFirstChild,
          readContextDuringReconcilation(returnFiber, context, lanes),
          lanes,
        );
      }

      // 无效的对象类型,抛出错误
      throwOnInvalidObjectType(returnFiber, newChild);
    }

    // 处理 children 为文本和数值的情况
    if (
      (typeof newChild === 'string' && newChild !== '') ||
      typeof newChild === 'number'
    ) {
      return placeSingleChild(
        reconcileSingleTextNode(
          returnFiber,
          currentFirstChild,
          // 如果 newChild 是数值, 转换为字符串
          '' + newChild,
          lanes,
        ),
      );
    }

    // 删除剩余子节点
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
  ): Fiber | null {
    // 这是入口方法, 根据 newChild 类型进行对应处理
    thenableIndexCounter = 0;
    const firstChildFiber = reconcileChildFibersImpl(
      returnFiber,
      currentFirstChild,
      newChild,
      lanes,
    );
    thenableState = null;
    return firstChildFiber;
  }

  return reconcileChildFibers;
}

// TODO ....