Fiber节点如何被创建?
如何构建一棵Fiber树?

render 流程

render阶段开始于performSyncWorkOnRoot或者performConcurrentWorkOnRoot的方法调用。具体的调用方法取决与是同步还是异步的

performSyncWorkOnRoot:

// performSyncWorkOnRoot同步调用会执行这个方法
function workLoopSync() {
    while(workInProgress !== null) {
        // do something
        performUnitOfWork(workInProgress)
    }
}
//performConcurrentWorkOnRoot异步调用执行
function workLoopConcurrent() {
    while(workInProgress !== null && !shouldYield()) {
        performUnitOfWork(workInProgress)
    }
}
  • 同步调用和异步调用的区别在于是否调用shouldYield(),用于判断当前关键帧是否还有剩余时间;如果当前浏览器帧没有剩余时间的话,shouldYield则会中止循环过程,直到浏览器有剩余时间
  • workInProgress代表当前已经创建的workInProgress Fiber
  • preformUnitOfWork方法会创建下一个Fiber节点并赋值给wornInProgress,并将workInProgress与已经创建的Fiber 节点连接起来构成Fiber 树
  • Fiber Reconciler来自于Stack Reconciler,通过遍历的方式实现可以中断的递归
  • workLoopConcurrent调用的主要方法就是performUnitOfWork, performUnitOfWork也因此分为两个阶段

'递'

  • 从rootFiber开始,向下,深度优先遍历(栈结构)。为遍历到的每一个Fiber 节点调用beginWork
  • beginWork会根据传入的Fiber 节点创建一个子Fiber节点,并将这两个节点链接起来
  • 当遍历到子节点的时候就会进行到归的阶段

'归'

  • 在这一阶段会调用completeWork来处理Fiber节点
  • 当某个Fiber节点执行完completeWork,如果存在其兄弟节点(fiber.sibling !== null),将会进入兄弟Fiber的‘递’阶段
  • 递****归的过程会交错执行,一直到归到最终的rootFiber。最后,render阶段结束

Example:

使用上一节用到的FunctionComponent:

function App() {
    return (
        <div>
            hello
            <span>React</span>
        </div>
    )
}

Fiber树结构如下:

render阶段执行过程:

  1. rootFiber beginWork
  2. App Fiber beginWork
  3. div Fiber beginWork
  4. "Hello" Fiber beginWork
  5. "Hello" Fiber completeWork
  6. span Fiber beginWork
    7. "React" Fiber beginWork // React会优化单一文本节点,所以并不会出现
    8. "React" Fiber completeWork
  7. span Fiber completeWork
  8. div Fiber completeWork
  9. App Fiber completeWork
  10. rootFiber completeWork

performUnitOfWork递归写法:

function performUnitOfWork(fiber)  {
    // beginWork()
    if (fiber.child) {
        performUnitOfWork(fiber.child)
    }
    // completeWork
    if (fiber.sibling) {
        performUnitOfWork(fiber.sibling)
    }
}

beginWork

beginWork的工作是传入当前的fiber 节点,创建子Fiber节点

function beginWork(
    current: Fiber | null, // 
    workInProgress: Fiber,
    renderLanes: Lanes
): Fiber | null {
    // do something
}

参数:

  • current: 当前组件对应的Fiber节点在上一次更新时候的 Fiber----workInProgress.alternate
  • workInProgress: 当前组件对应的Fiber节点
  • renderLanes: 优先级

在双缓存机制这一块有提到,组件mount的时候,因为是在首屏渲染,此时页面并没有挂载DOM,是不存在当前组件对应的Fiber 节点在上次更新时的Fiber 节点的,也就是说,在mount的时候current === null

current === null来判断组件当前是在mount还是update

基于组件所处的不同状态(mount和update),beginWork可以分为两个部分:

  • mount时: 除fiberRootNode之外,current === null。会根据fiber.tag的不同,创建不同的子Fiber节点
  • update时:如果current存在,在满足条件的时候可以复用current节点,克隆current.child作为workInProgress.child ,而不需要新建一个workInProgress.child
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes
): Fiber | null {

  // update时:如果current存在可能存在优化路径,可以复用current(即上一次更新的Fiber节点)
  if (current !== null) {
    // ...省略

    // 复用current
    return bailoutOnAlreadyFinishedWork(
      current,
      workInProgress,
      renderLanes,
    );
  } else {
    didReceiveUpdate = false;
  }

  // mount时:根据tag不同,创建不同的子Fiber节点
  switch (workInProgress.tag) {
    case IndeterminateComponent: 
      // ...省略
    case LazyComponent: 
      // ...省略
    case FunctionComponent: 
      // ...省略
    case ClassComponent: 
      // ...省略
    case HostRoot:
      // ...省略
    case HostComponent:
      // ...省略
    case HostText:
      // ...省略
    // ...省略其他类型
  }
}

update

在update的时候可以直接复用前一次更新的子Fiber,不需要新建子Fiber的情况,即didReceiveUpdate === false

if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      didReceiveUpdate = true;
    } else if (!includesSomeLane(renderLanes, updateLanes)) {
      didReceiveUpdate = false;
      switch (workInProgress.tag) {
        // 省略处理
      }
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderLanes,
      );
    } else {
      didReceiveUpdate = false;
    }
  } else {
    didReceiveUpdate = false;
  }
  1. 当oldProps === newProps && workInProgress.type === current.type,也就是在type和props不变的情况下
  2. !includesSomeLane(renderLanes, updateLanes),当前的Fiber节点优先级较低的时候

mount

当不满足优化的路径的时候,就会去新建子Fiber
根据fiber.tag的不同,进入不同类型的Fiber去创建逻辑

// mount时:根据tag不同,创建不同的Fiber节点
switch (workInProgress.tag) {
  case IndeterminateComponent: 
    // ...省略
  case LazyComponent: 
    // ...省略
  case FunctionComponent: 
    // ...省略
    return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      )
  case ClassComponent: 
    // ...省略
    return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      )
  case HostRoot:
    // ...省略
  case HostComponent:
    return updateHostComponent(current, workInProgress, renderLanes);
  case HostText:
    // ...省略
  case SuspenseComponent:
     // ...省略 
  case HostPortal:
    // ...省略
  case ForwardRef: 
    // ...省略
  case Fragment:
    // ...省略
  case Mode:
    // ...省略
  case Profiler:
    // ...省略
  case ContextProvider:
    // ...省略
  case ContextConsumer:
    // ...省略
  // ...省略其他类型
}

React组件类型TagType

对于常见的组件类型: FunctionComponent/ClassComponent/HostComponent,他们分别调用了updateFunctionComponent/updateClassComponent/updateHostComponent,根据源码找到最终调用了reconcileChildren

reconcilerChilren

作为Reconciler模块的核心方法:

  • 对于mount的组件,他会创建一个新的子Fiber节点
  • 对于update的组件,他会将当前组件与该组件上次更新时对应时Fiber 节点比较(diff算法),将比较的结果生成新的Fiber节点
function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  if (current === null) {
    // If this is a fresh new component that hasn't been rendered yet, we
    // won't update its child set by applying minimal side-effects. Instead,
    // we will add them all to the child before it gets rendered. That means
    // we can optimize this reconciliation pass by not tracking side-effects.
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else {
    // If the current child is the same as the work in progress, it means that
    // we haven't yet started any work on these children. Therefore, we use
    // the clone algorithm to create a copy of all the current children.

    // If we had any progressed work already, that is invalid at this point so
    // let's throw it out.
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}

mountChildFibers和reconcileChildFibers都调用了同一个方法ChildReconciler,唯一不同的一点是reconcileChildFibers会为生成的Fiber节点带上flags属性,而mountChildFibers不会
最终他会生成新的子Fiber节点赋值给workInProgress.child,作为本次beginWork的返回值return workInProgress.child,并作为下次preformUnitOfWork下次执行的workInProgress = next传参

flags

  • 因为Renderer阶段是在内存中进行的,当工作结束之后会通知Renderer需要执行的DOM操作;
  • 需要执行的操作类型就是flags

ReactFiberFlags

beginWork流程图:

completeWork

组件执行beginWork之后会创建子Fiber节点,节点上面可能存在flags属性
completeWork

和beginWork类似,completeWork也是根据不同的类型fiber.tag处理不同的逻辑

function completeWork(
    current: Fiber | null,
    workInProgress: Fiber,
    renderLanes: Lanes
): Fiber | null {
    const newProps = workInProgress.pendingProps;
    switch (workInProgress.tag) {
        case IndeterminateComponent:
        case LazyComponent:
        case SimpleMemoComponent:
        case FunctionComponent:
        case ForwardRef:
        case Fragment:
        case Mode:
        case Profiler:
        case ContextConsumer:
        case MemoComponent:
        return null;
        case ClassComponent: {
        // ...省略
        return null;
        }
        case HostRoot: {
        // ...省略
        updateHostContainer(workInProgress);
        return null;
        }
        case HostComponent: {
        // ...省略
        return null;
        }
    }
// ...省略
}
  • 首先看一下与页面DOM渲染相关的Fiber节点----HostComponent

HostComponet

与上面所说的beginWork类似的是,我们同样根据current === null 判断当前页面是mount还是update,同时需要注意的是在update的时候Fiber节点是否存在对应的DOM节点,即workInProgress.stateNode !== null

case HostComponent: {
    popHostContext(workInProgress)
    const rootContainerInstance = getRootHostContainer()
    const type = workInPropgress.type

    if (current !== null && workInProgress.stateNode !== null) {
        // update,---> ## update
    } else {
        // mount
    }
    return null
}

update

当update的时候,Fiber节点已经存在对应的DOM节点,不需要生成DOM;主要工作在于处理props: 事件回调,style, children...

if (current !== null && workInProgress.stateNode !== null) {
    //update
    updateHostComponent(
        current,
        workInProgress,
        type,
        newProps,
        rootContainerInstance
    )
}

updateHostComponent方法定义

 const updatePayload = prepareUpdate(
      instance,
      type,
      oldProps,
      newProps,
      rootContainerInstance,
      currentHostContext,
    );
    // TODO: Type this specific to this type of component.
    workInProgress.updateQueue = (updatePayload: any);

在updateHostComponent内部props处理完了之后,会赋值给workInProgress.updateQueue = (updatePayload: any),并在最终commit阶段进行渲染

mount

在mount的时候主要逻辑:

  • 为Fiber节点生成对应的DOM节点
  • 将子节点插入生成的DOM节点中
  • 处理props

const currentHostContext = getHostContext();
// 为fiber创建对应DOM节点
const instance = createInstance(
    type,
    newProps,
    rootContainerInstance,
    currentHostContext,
    workInProgress,
  );
// 将子孙DOM节点插入刚生成的DOM节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM节点赋值给fiber.stateNode
workInProgress.stateNode = instance;

// 与update逻辑中的updateHostComponent类似的处理props的过程
if (
  finalizeInitialChildren(
    instance,
    type,
    newProps,
    rootContainerInstance,
    currentHostContext,
  )
) {
  markUpdate(workInProgress);
}

mount时只会在rootFiber存在PlaceMent``flags;
因为completeWork是处于归阶段调用的方法,每次调用appendAllChildren的时候,都会将已经生成的子DOM节点插入到当前的已经生成的DOM节点下。当归阶段执行到rootFiber时,我们已经可以得到一个构建好的DOM树

流程结束

至此,render阶段全部工作完成。在performSyncWorkOnRoot函数中fiberRootNode被传递给commitRoot方法,开启commit阶段工作流程。commitRoot

commitRoot(root)

complete 流程图