“递”阶段mount时流程

201 阅读5分钟

Fiber大家已经很熟悉了在这里就不再介绍了,简单的总结一下新老架构

新老架构简介

  • 老的架构使用递归更新会有两个缺点:
    1. 当层级深的时候,递归更新时间超过16ms,用户会感觉卡顿。
    2. 因为递归更新无法中断,假设遍历发生了中断,虽然可以保留当下进行中节点的索引,下次继续时,我们的确可以继续遍历该节点下面的所有子节点,但是没有办法找到其父节点——因为每个节点只有其子节点的指向。断点没有办法恢复,只能从头再来一遍。
  • 新架构的改变:
    1. 增加Scheduler(调度器),可以组件渲染的工作分片,到时会主动让出渲染主线程。Scheduler还提供了多种调度优先级供任务设置。
    2. Reconciler内部采用Fiber(纤程)架构,用链表取代了,记录了子节点(child)、兄弟组节点(sibling)、父节点(return),生成Fiber树
    3. 双缓存Fiber树
      • current Fiber树:屏幕上显示内容对应的Fiber树
      • workInProgress Fiber树:正在内存中构建的Fiber树
      • 多次调用ReactDOM.render渲染不同的组件树,他们会拥有不同的rootFiber。但是整个应用的根节点只有一个,那就是fiberRootNode
      • workInProgress Fiber 树render阶段完成构建后进入commit阶段渲染到页面上。渲染完毕后,workInProgress Fiber 树变为current Fiber 树。即fiberRootNode.current = rootFiber

“递”阶段mount时流程

“递”阶段

首先从rootFiber开始向下深度优先遍历。为遍历到的每个Fiber节点调用beginWork方法 方法。

该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。

当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。

“归”阶段

在“归”阶段会调用 completeWork 处理Fiber节点

当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(即fiber.sibling !== null),会进入其兄弟Fiber的“递”阶段。

如果不存在兄弟Fiber,会进入父级Fiber的“归”阶段。

“递”和“归”阶段会交错执行直到“归”到rootFiber。至此,render阶段的工作就结束了。

例子

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );

对应的”递“”归”过程如下图:

beginWork

方法定义

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  // ...省略函数体
}
  • current:当前组件对应的Fiber节点在上一次更新时的Fiber节点,即workInProgress.alternate
  • workInProgress:当前组件对应的Fiber节点
  • renderLanes:优先级相关,在讲解Scheduler时再讲解

双缓存机制我们知道,除rootFiber以外, 组件mount时,由于是首次渲染,是不存在当前组件对应的Fiber节点在上一次更新时的Fiber节点,即mountcurrent === null

组件update时,由于之前已经mount过,所以current !== null

所以我们可以通过current === null ?来区分组件是处于mount还是update

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:
      // ...省略
    // ...省略其他类型
  }
}

mount时

我们以上面代码中divbeginWork为例子

因为是current fiber为空下一步这是进入switch(workInProgress.tag)

updateHostComponent

div workInProgress.tagHostComponent,进入updateHostComponent

shouldSetTextContent方法判断当前Fiber节点是否只有唯一一个文本子节点,如果结果为true就不会为文本子节点单独创建一个Fiber节点,这也是react的一个优化策略

reconcileChildren

updateHostComponent方法返回一个workInProgress.child,当reconcileChildren没有执行时,workInProgress.childnull,所以reconcileChildren会为当前workInProgress创建一个child节点。

reconcileChildren方法通过判断current === null进入mountChildFibers还是reconcileChildFibers

在源码ReactChildFiber.old.js文件中我们可以看到

mountChildFibersreconcileChildFibers方法调用的都是ChildReconciler这是调用的时候传递的boolean不同

ChildReconciler

shouldTrackSideEffects表示是否追踪副作用

什么是副作用

shouldTrackSideEffects === false时,会直接return 当shouldTrackSideEffects === true时,会将要删除的Fiber节点flags打上Deletion标记

扩展:为什么effectTag用的是二进制表示?

ReactFiberFlags.js文件保存了所有的副作用(render阶段不会执行DOM操作只会给相应的Fiber节点打上标记)

通过二进制表示effectTag,可以方便的使用位操作fiber.flags赋值多个effect

继续ChildReconciler

ChildReconciler会判断newChild(当前Fiber节点的子节点)是否是一个Object或者是stringnumber或是否为一个Array

  • Object表示只有一个子节点
  • stringnumber表示文本子节点
  • Array表示有多个子节点

多个子节点时,每次beginWork只会为当前节点创建一个子节点,会进行多次beginWork

newChildObject,并且newChild.$$typeofREACT_ELEMENT_TYPE 进入placeSingleChild方 法

placeSingleChild

createFiberFromElement

通过调用createFiberFromTypeAndProps创建一个Fiber节点并返回。

createFiberFromTypeAndProps

fiberTag赋值,调用createFiber方法创建Fiber节点

createFiber

实例化一个Fiber节点并返回

div mount大致流程

总结

  • mount时的beginWork就是为当前Fiber节点创建一个子Fiber节点
  • 每次beginWork只会创建一个子Fiber节点
  • 当前Fiber节点的所有子Fiber节点beginWork后会进行CompleteWrk`

参考资料

React技术揭秘: react.iamkasong.com/process/rec…