Skip to content

Latest commit

 

History

History
185 lines (133 loc) · 8.16 KB

fiber-and-fiberRoot.md

File metadata and controls

185 lines (133 loc) · 8.16 KB

源码解析一 Fiber and FiberRoot

Fiber出来之后,新的调和系统称为fiber reconciler,为了识别,我们把老的调和系统称为stack reconciler。 每一个ReactElement,都会生成相应的工作单元(我们把每个工作单元称为Fiber),根据这些节点的层级关系,会生成整个Fiber树。

我们的整个调和过程,都是围绕Fiber树展开的

stack reconcilerfiber reconciler

stack reconciler中,整个VDOM树 如下图:

如果某个节点setState,会从当前节点开始,一直递归到整个dom树的最底层,找到需要修改的信息,并传递给renderer进行渲染。

这整个过程在react中是通过递归实现,一气呵成,连续不可中断,具体的实现可以查看15.0分支代码。如果需要渲染的组件树过于庞大,js执行占据主线程时间较长,导致页面响应度变差,就会很明显造成卡顿现象,整个过程类似下图:

为了解决这个问题,React重构了整个调和架构,称为新的fiber reconciler,它支持:

  1. 对每个任务划分优先级,优先级通过事件判定,根据优先级不同分配到不同的 Task 中处理,每个 Task 只处理对于当前最紧急的任务
  2. 能够把任务切片处理,拆分任务并能调度的能力
  3. 能够调整优先级,重置或者复用任务

整个过程类似下图:

以上的3点都要求整个调度的过程可拆分,可中断。中断后,在恢复时,对于之前的节点,可能当时优先级不高,跳过了,但是现在在新的时间下,可能优先级有所改变,因此对这些节点需要重新进行调度,为了满足这样的需求,在新的架构中,React将整个树结构用链表来实现,每个节点都记录了它的关系信息,有了关系信息,就可以很轻易的通过某一个节点,复现整个树。新的Fiber树如下图

FiberRoot

FiberRoot是整个Fiber树的根节点,无论是更新还是渲染,都是从根节点开始的,所以FiberRoot上记录着整个Fiber树的调度信息,可以将FiberRoot理解为一次调度任务的起点,在fiber reconciler中,在全局维护着一条关于FiberRoot的队列,如下图,每次在更新渲染时,都会更新当前 fiberRoot的优先级,并塞到任务队列的末尾

               nextScheduledRoot            nextScheduledRoot            nextScheduledRoot
firstFiberRoot -----------------> FiberRoot -----------------> FiberRoot -----------------> lastFiberRoot
               <---------------------------------------------------------------------------
                                            nextScheduledRoot 
class FiberRoot {
  containerInfo: Element  // 容器节点
  current: Fiber // 当前根节点的 fiber

  // 最老和最新的被挂起的任务的优先级
  earliestSuspendedTime: ExpirationTime = NoWork
  latestSuspendedTime: ExpirationTime = NoWork

  // 最老和最新的不确定是否挂起的任务的优先级
  earliestPendingTime: ExpirationTime = NoWork
  latestPendingTime: ExpirationTime = NoWork

  // 最新的通过一个promise被reslove并且可以重新尝试的优先级
  latestPingedTime: ExpirationTime = NoWork

  didError: boolean = false

  // 等待提交的优先级
  pendingCommitExpirationTime: ExpirationTime = NoWork

  // 完成render后的 fiber 树
  finishedWork: Fiber = null

  // 挂起任务的timeout 标志位
  timeoutHandle: number = noTimeout

  // 下一个 work 的优先级与当前优先级
  nextExpirationTimeToWorkOn: ExpirationTime = NoWork
  expirationTime: ExpirationTime = NoWork

  firstBatch: Batch = null

  nextScheduledRoot: FiberRoot = null

  constructor(containerInfo: Element, isConcurrent: boolean) {
    const mode = isConcurrent ? ConcurrentMode : NoContext // 区分异步还是同步

    this.current = new Fiber(HostRoot, null, null, mode)
    this.containerInfo = containerInfo
    this.current.stateNode = this
  }
}

Fiber

Fiber是一个工作单元,由于一切的调度更新渲染都是围绕着它展开,在fiber reconciler下,操作是可以分成很多小部分,并且可以被中断的,所以同步操作DOM可能会导致Fiber树与实际DOM的不同步。对于每个节点,不光需要存储了对应元素节点的基本信息,还要保存一些用于任务调度的信息,以及跟周围节点的关系信息

class Fiber {
  // 组件类型 如:function、class、hostComponent 等
  tag: WorkTag
  // ReactElment 里的 key
  key: null | string
  // ReactElement.type,也就是我们调用`createElement`的第一个参数
  elementType: any = null
  // 异步组件resolved之后返回的内容,一般是`function`或者`class`
  type: any = null
  // 自身特性,如:class就是当前 的组件对象,hostComponent 就是 dom 元素
  stateNode: any = null

  return: Fiber = null // 父节点
  child: Fiber = null // 子节点
  sibling: Fiber = null // 右边的兄弟节点
  index: number = 0 // 索引值

  ref: any = null

  pendingProps: any // 未处理的 props,可以理解为 new props
  memoizedProps: any = null // 当前节点的 props,可以理解为 old props

  updateQueue: UpdateQueue<any> = null // 当前节点的任务队列
  memoizedState: any = null // 当前节点的state

  contextDependencies: ContextDependencyList = null // context 队列

  mode: TypeOfMode // 工作类型, NoContext:同步渲染 ConcurrentMode:异步渲染

  effectTag: SideEffectTag = NoEffect // 标记当前节点的更新类型

  nextEffect: Fiber = null 
  firstEffect: Fiber = null
  lastEffect: Fiber = null

  expirationTime: ExpirationTime = NoWork // 优先级
  childExpirationTime: ExpirationTime = NoWork // 子优先级

  alternate: Fiber = null // 用于调度时的快照

  constructor(tag: WorkTag, pendingProps: any, key: string | null, mode: TypeOfMode) {
    this.tag = tag
    this.pendingProps = pendingProps
    this.key = key
    this.mode = mode
  }
}

workInProgress

WorkInProgressFiber进行调度时生成的一个副本,与Fiber通过alternate相互连接,

为什么需要副本

由于现在的任务都是可中断的,如果直接在原树上操作,若此时需要中断,归还控制权,那么更新到一半的Fiber树该怎么办,保留,回滚?为了解决这个问题,在Fiber reconciler中,Mount或者Update时,都会基于当前 Fiber树生成一棵新的WorkInProgress树(副本树),调度完成后用整个WorkInProgress树替代当前的Fiber树,成为当前的Fiber

function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress: Fiber = current.alternate

  if (workInProgress === null) {
    const { tag, key, mode } = current
    workInProgress = new Fiber(tag, pendingProps, key, mode)

    workInProgress.elementType = current.elementType
    workInProgress.type = current.type
    workInProgress.stateNode = current.stateNode

    workInProgress.alternate = current
    current.alternate = workInProgress
  } else {
    // 如果有 alternate 了,把几个调度需要的属性初始化
    workInProgress.pendingProps = pendingProps

    workInProgress.effectTag = NoEffect
    workInProgress.nextEffect = null
    workInProgress.firstEffect = null
    workInProgress.lastEffect = null
  }

  workInProgress.childExpirationTime = current.childExpirationTime
  workInProgress.expirationTime = current.expirationTime

  workInProgress.child = current.child
  workInProgress.sibling = current.sibling

  workInProgress.index = current.index
  workInProgress.ref = current.ref

  workInProgress.memoizedProps = current.memoizedProps
  workInProgress.memoizedState = current.memoizedState
  workInProgress.updateQueue = current.updateQueue

  workInProgress.contextDependencies = current.contextDependencies

  return workInProgress
}