React fiber 架构详细解读 16.13.1版本

3,911 阅读16分钟

本文是一篇入门级源码分析类文章,对于源码了解较深者也可以阅读本文多多指点笔者一二。

(本文不涉及优先级,事件系统,requestIdleCallback的polyfill,hooks等原理的解释)

好文推荐:

  1. 源码基础
  2. 事件系统
  3. React Fiber理论篇
  4. React Hook源码

从一段简单的代码开始分析

import React from 'react';
import ReactDOM from 'react-dom';

const vdomTree = <div>
  <article>
    <span>1</span>
  </article>
  <p>
    <strong>s</strong>
  </p>
  <a >a</a>
</div>;

ReactDOM.render(
  vdomTree,
  document.getElementById('root')
);

为什么我们要引入React?

这个问题很简单因为以上jsx代码通过@babel/preset-react这样的工具就会自动生成如下代码,你也可以自己在线试一试

const vdomTree = /*#__PURE__*/
  React.createElement("div", null,
    /*#__PURE__*/React.createElement("article", null,
      /*#__PURE__*/React.createElement("span", null, "1")
    ),
    /*#__PURE__*/React.createElement("p", null,
      /*#__PURE__*/React.createElement("strong", null, "s")
    ),
    /*#__PURE__*/React.createElement("a", null, "a")
  );

ReactDOM.render(
  vdomTree,
  document.getElementById('root')
);

我们来打印一下这个vdomTree:

vdomtree结构图

我们用一张图来表示:

每个节点有几个比较重要的属性我们来看一下:

{
    ?typeof: '通过createElement这个方法创建的都是reactElement类型',
    type: '第一个参数',
    props:{
        children:'当子节点有多个时为一个数组,当只有一个时就是reactElement'
    }
}

react是怎么定义fiber结构的?

很多解析源码的文章都说fiber是一个链表结构,其实fiber是用单链表组成的树结构,在源码中这个位置有介绍:

所以基于此处我们应该学习一个数据结构的小知识

如何将一颗树转换成二叉树

这是我在大学的数据结构课本上截的一张图,其实就是将节点的左指针指向第一个孩子节点,右指针指向相邻的兄弟节点即可:

那么这个和fiber结构又有什么关系呢?

fiber结构图

这里与上面的vdomTree图相比较我来画出fiber的结构图,提前给大家看一下:

其实这里我们不难发现这里react里面就是把普通的树转换成了二叉树,有点区别的是每个子节点都有一个指向父节点的return指针,这里用虚线表示

那么react是怎么生成这个fiber结构的呢?我们接着往下看

代码:普通树转化成二叉树

那么把树转换成二叉树的代码该怎么写呢?

vdomTree代码结构如下

let vdomtree = {
    type:'div'
    children:[{
        type:'article',
        children:{
            type:'span',
            children:null
        }
    },{
        type:'p',
        children:{
            type:'strong',
            children:null
        }
    },{
        type:'a',
        children:null
    }]
}

将以上数据格式转换成fiberTree格式代码如下:

//为什么要初始化两个全局变量?

//为了遍历的方便性,我们先初始化一个头节点,将基础数据结构定义好
let workInProgress = {
  tag: 'root',
  type: '',
  child: null,
  sibling:null,
  return: null,
  vChildren: vdomtree
};
//由于上面的头节点是动态的,当我们完成转换后,需要读取到这课树
//所以还需要一个指针来保存第一个头节点
const root = {
  current:workInProgress
};
//开始进行工作
function workLoop(){
  while (workInProgress){
    workInProgress = performUnitOfWork(workInProgress);
  }
  console.log(root);
}
//这个函数做了两件事:
//1. beginWork负责创建
//2. completeUnitOfWork负责调整遍历顺序,在源码中此处还会做一些其他事情
function performUnitOfWork(unitOfWork) {
  let next = beginWork(unitOfWork)
  if(!next){
    next = completeUnitOfWork(unitOfWork);
  }
  return next;
}
//开始生成fiber节点
function beginWork(unitOfWork) {
  return updateHostDom(unitOfWork)
}
//当没有子节点时移动到兄弟节点,没有兄弟节点就移动到父节点的兄弟节点
function completeUnitOfWork(unitOfWork) {
  do {
    let siblingFiber = unitOfWork.sibling;
    if (siblingFiber !== null) {
      return siblingFiber;
    }
    unitOfWork = unitOfWork.return;
  } while (unitOfWork);
  return null;
}
//生成后续节点
function updateHostDom(unitOfWork) {
  let vdom = unitOfWork.vChildren;
  if(Array.isArray(vdom)){
    let firstFiber = null;
    let preFiber = null;
    for (let i = 0;i < vdom.length;i++){
      if(!firstFiber){
        firstFiber = {
          tag: 'dom',
          type: vdom[i].type,
          child: null,
          sibling:null,
          return: unitOfWork,
          vChildren: vdom[i].children
        }
        preFiber = firstFiber;
      }else {
        preFiber.sibling = {
          tag: 'dom',
          type: vdom[i].type,
          child: null,
          sibling:null,
          return: unitOfWork,
          vChildren: vdom[i].children
        }
        preFiber = preFiber.sibling;
      }
    }
    return firstFiber;
  }else {
    return {
      tag: 'dom',
      type: vdom.type,
      child: null,
      sibling:null,
      return: unitOfWork,
      vChildren: vdom.children
    }
  }
}

workLoop();

看懂以上代码react就是基于此来转换的

生成顺序如下图所示:

从render函数开始分析

打开render函数可以看到它内部就是调用了一下legacyRenderSubtreeIntoContainer函数,这个函数只做了两个事情:

  1. 生成fiberRoot
  2. 根据fiberRoot和传入的vdomtree渲染页面
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
    var root = container._reactRootContainer;
    var fiberRoot;

    if (!root) {
      // Initial mount生成fiberRoot和Rootfiber
      root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container,forceHydrate);
      fiberRoot = root._internalRoot;
      //开始进行更新,由于是初始化所以不需要批处理技术
      unbatchedUpdates(function () {
        updateContainer(children, fiberRoot, parentComponent, callback);
      });
    } else {
        //如果不是第一次更新那么可以复用上次的fiberRoot
      fiberRoot = root._internalRoot;
      updateContainer(children, fiberRoot, parentComponent, callback);
    }
  }

头指针创建

根据这个函数调用栈我们发现最终调用了createFiberRoot这个方法:

function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
    var root = new FiberRootNode(containerInfo, tag, hydrate);
    var uninitializedFiber = createHostRootFiber(tag);
    root.current = uninitializedFiber;
    uninitializedFiber.stateNode = root;
    //在HostRootFiber上初始化一个updateQueue队列
    initializeUpdateQueue(uninitializedFiber);
    return root;
  }

看到这个其实我们就已经知道react在生成fiber架构前会先生成这两个节点,结构如下:

fiberRoot以及HostRootFiber的具体属性

源码在这

fiberRoot

type BaseFiberRootProperties = {|
  // The type of root (legacy, batched, concurrent, etc.)
  // 
  tag: RootTag,

  // Any additional information from the host associated with this root.
  containerInfo: any,
  // Used only by persistent updates.
  pendingChildren: any,
  // The currently active root fiber. This is the mutable root of the tree.
  current: Fiber,

  pingCache: WeakMap<Wakeable, Set<mixed>> | Map<Wakeable, Set<mixed>> | null,

  // A finished work-in-progress HostRoot that's ready to be committed.
  finishedWork: Fiber | null,
  // Timeout handle returned by setTimeout. Used to cancel a pending timeout, if
  // it's superseded by a new one.
  timeoutHandle: TimeoutHandle | NoTimeout,
  // Top context object, used by renderSubtreeIntoContainer
  context: Object | null,
  pendingContext: Object | null,
  // Determines if we should attempt to hydrate on the initial mount
  +hydrate: boolean,

  // Used by useMutableSource hook to avoid tearing during hydration.
  mutableSourceEagerHydrationData?: Array<
    MutableSource<any> | MutableSourceVersion,
  > | null,

  // Node returned by Scheduler.scheduleCallback. Represents the next rendering
  // task that the root will work on.
  callbackNode: *,
  callbackPriority: LanePriority,
  eventTimes: LaneMap<number>,
  expirationTimes: LaneMap<number>,

  pendingLanes: Lanes,
  suspendedLanes: Lanes,
  pingedLanes: Lanes,
  expiredLanes: Lanes,
  mutableReadLanes: Lanes,

  finishedLanes: Lanes,

  entangledLanes: Lanes,
  entanglements: LaneMap<Lanes>,
|};

HostRootFiber

export type Fiber = {|
  // These first fields are conceptually members of an Instance. This used to
  // be split into a separate type and intersected with the other Fiber fields,
  // but until Flow fixes its intersection bugs, we've merged them into a
  // single type.

  // An Instance is shared between all versions of a component. We can easily
  // break this out into a separate object to avoid copying so much to the
  // alternate versions of the tree. We put this on a single object for now to
  // minimize the number of objects created during the initial render.

  // Tag identifying the type of fiber.
  //fiber类型
  tag: WorkTag,

  // Unique identifier of this child.
  //就是你熟悉的key
  key: null | string,

  // The value of element.type which is used to preserve the identity during
  // reconciliation of this child.
  // createElement的第一个参数
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  // 异步组件
  type: any,

  // The local state associated with this fiber.
  // 当前Fiber状态比如dom
  stateNode: any,

  // Conceptual aliases
  // parent : Instance -> return The parent happens to be the same as the
  // return fiber since we've merged the fiber and instance.

  // Remaining fields belong to Fiber

  // The Fiber to return to after finishing processing this one.
  // This is effectively the parent, but there can be multiple parents (two)
  // so this is only the parent of the thing we're currently processing.
  // It is conceptually the same as the return address of a stack frame.
  // 父fiber
  return: Fiber | null,

  // Singly Linked List Tree Structure.
  // 子
  child: Fiber | null,
  //兄弟
  sibling: Fiber | null,
  index: number,

  // The ref last used to attach this node.
  // I'll avoid adding an owner field for prod and model that as functions.
  //你熟悉的ref
  ref:
    | null
    | (((handle: mixed) => void) & {_stringRef: ?string, ...})
    | RefObject,

  // Input is the data coming into process this fiber. Arguments. Props.
  // 更新的状态
  pendingProps: any, // This type will be more specific once we overload the tag.
  //上次的props
  memoizedProps: any, // The props used to create the output.

  // A queue of state updates and callbacks.
  // Update产生的状态
  updateQueue: mixed,

  // The state used to create the output
  //上次的state
  memoizedState: any,

  // Dependencies (contexts, events) for this fiber, if it has any
  dependencies: Dependencies | null,

  // Bitfield that describes properties about the fiber and its subtree. E.g.
  // the ConcurrentMode flag indicates whether the subtree should be async-by-
  // default. When a fiber is created, it inherits the mode of its
  // parent. Additional flags can be set at creation time, but after that the
  // value should remain unchanged throughout the fiber's lifetime, particularly
  // before its child fibers are created.
  // 这是一个比较复杂的节点
  mode: TypeOfMode,

  // Effect
  //标记effect状态
  effectTag: SideEffectTag,
  subtreeTag: SubtreeTag,
  //需要删除的fiber
  deletions: Array<Fiber> | null,

  // Singly linked list fast path to the next fiber with side-effects.
  // 为了查找下个effect fiber
  nextEffect: Fiber | null,

  // The first and last fiber with side-effect within this subtree. This allows
  // us to reuse a slice of the linked list when we reuse the work done within
  // this fiber.
  // 子树的第一个fiber
  firstEffect: Fiber | null,
  // 子树的最后一个fiber
  lastEffect: Fiber | null,

  lanes: Lanes,
  childLanes: Lanes,

  // This is a pooled version of a Fiber. Every fiber that gets updated will
  // eventually have a pair. There are cases when we can clean up pairs to save
  // memory if we need to.
  // 可以理解成缓存
  alternate: Fiber | null,

  // Time spent rendering this Fiber and its descendants for the current update.
  // This tells us how well the tree makes use of sCU for memoization.
  // It is reset to 0 each time we render and only updated when we don't bailout.
  // This field is only set when the enableProfilerTimer flag is enabled.
  actualDuration?: number,

  // If the Fiber is currently active in the "render" phase,
  // This marks the time at which the work began.
  // This field is only set when the enableProfilerTimer flag is enabled.
  actualStartTime?: number,

  // Duration of the most recent render time for this Fiber.
  // This value is not updated when we bailout for memoization purposes.
  // This field is only set when the enableProfilerTimer flag is enabled.
  selfBaseDuration?: number,

  // Sum of base times for all descendants of this Fiber.
  // This value bubbles up during the "complete" phase.
  // This field is only set when the enableProfilerTimer flag is enabled.
  treeBaseDuration?: number,

  // Conceptual aliases
  // workInProgress : Fiber ->  alternate The alternate used for reuse happens
  // to be the same as work in progress.
  // __DEV__ only
  _debugID?: number,
  _debugSource?: Source | null,
  _debugOwner?: Fiber | null,
  _debugIsCurrentlyTiming?: boolean,
  _debugNeedsRemount?: boolean,

  // Used to verify that the order of hooks does not change between renders.
  _debugHookTypes?: Array<HookType> | null,
|};

performSyncWorkOnRoot

此时我们可以发现updateContainer最终会调用它 performSyncWorkOnRoot

//删减版
  function performSyncWorkOnRoot(root) {
    //...删掉了expirationTime的计算和fiber优先级的处理
    if (root !== workInProgressRoot || expirationTime !== renderExpirationTime$1) {
    //初始化一些操作
      prepareFreshStack(root, expirationTime);
    } // If we have a work-in-progress fiber, it means there's still work to do
    // in this root.
    //开始调度
    do {
        try {
          workLoopSync();
          break;
        } catch (thrownValue) {
          handleError(root, thrownValue);
        }
    } while (true);
  }

我们可以发现这个函数做了两件事:

  1. 初始化
  2. 调度

prepareFreshStack初始化

这个函数很简单只是将fiberRoot赋值给workInProgressRoot,将rootFiber赋值给workInProgress(可以这么理解,内部做了缓存处理)

调度函数workLoopSync

这个函数就是不断使用performUnitOfWork处理workInProgress,下面我们来看下我截取的部分核心逻辑:

function workLoopSync() {
    // Already timed out, so perform work without checking if we need to yield.
    while (workInProgress !== null) {
      workInProgress = performUnitOfWork(workInProgress);
    }
}
  function performUnitOfWork(unitOfWork) {
    var current = unitOfWork.alternate;
    var next = beginWork(current, unitOfWork);
    if (next === null) {
      next = completeUnitOfWork(unitOfWork);
    }
    return next;
  }
  function completeUnitOfWork(unitOfWork) {
    workInProgress = unitOfWork;
    do {
        //...删除了收集effect的代码
      var siblingFiber = workInProgress.sibling;

      if (siblingFiber !== null) {
        // If there is more work to do in this returnFiber, do that next.
        return siblingFiber;
      } // Otherwise, return to the parent


      workInProgress = returnFiber;
    } while (workInProgress !== null); // We've reached the root.
    return null;
  }

以上代码其实和刚刚那个思考题是一样的逻辑来生成fiber结构,此时结构图如下所示:

react源码比刚刚笔者自己写的代码多了3个主要逻辑:

  1. 依赖收集
  2. diff比对(更新阶段)
  3. 时间分片机制(更新阶段)

依赖收集(副作用收集)

此处我们要先搞明白一件事,到底收集的是什么?(收集需要更新的fiber节点)

其实我们在初始化生成fiber结构时,我们会给每个节点都标记成新增节点,此处我们会判断节点是否有firstEffect,如果有那么这便是子节点的副作用,即子节点,并且把它挂载到副节点上,继续判断自身是否存在effectTag标记,如果有那么自身也是一个副作用节点,继续把自己挂载到父节点,最终形成一个链表结构。

(上面一段话就是react依赖收集思路,不过react会判断优先级来插入到链表中不同的位置)

依赖收集的顺序

依赖收集是发生在fiber树的构建过程的,在completeUnitOfWork函数中我注释掉的那部分,所以顺序应该是这样的:

diff比对

这是一个可以单独拿出来说一下的,有时间再写,不过这里值得一读的是官方提供的文档,这里可以说是写的很详细了,具体实现在源码ReactChildfiber.js这个文件中的ChildReconciler方法中:

// This wrapper function exists because I expect to clone the code in each path
// to be able to optimize each path individually by branching early. This needs
// a compiler or we can do it manually. Helpers that don't need this branching
// live outside of this function.
function ChildReconciler(shouldTrackSideEffects) {
  function deleteChild(returnFiber: Fiber, childToDelete: Fiber)

  function deleteRemainingChildren

  function mapRemainingChildren

  function useFiber

  function placeChild

  function placeSingleChild

  function updateTextNode

  function updateElement

  function updatePortal
  
  function updateFragment

  function createChild

  function updateSlot

  function updateFromMap

  /**
   * Warns if there is a duplicate or missing key
   */
  function warnOnInvalidKey

  function reconcileChildrenArray

  function reconcileChildrenIterator

  function reconcileSingleTextNode

  function reconcileSingleElement

  function reconcileSinglePortal

  // This API will tag the children with the side-effect of the reconciliation
  // itself. They will be added to the side-effect list as we pass through the
  // children and the parent.
  function reconcileChildFibers

  return reconcileChildFibers;
}

在这个函数上面有一段注释,大致意思就是这是一个包裹着很多不同分支的reconcile代码,这样react官方会做很多针对性的优化。

时间分片

这其实用一个新的api就可以实现,不过react实现了自己的polyfill版本,可以下次再说react是怎么实现的,这里可以简单看一下这个api requestIdleCallback其实很简单: 假如你的页面有这么一个动画:

div{
    width: 100px;
    height: 100px;
    background-color: red;
    transition: height 20s ease;
}
div:hover {
    height: 3000px;
}

当用户打开页面鼠标移动到div上时,这时页面有一个work计算在页面打开后2s时执行,耗时5秒:

function work(allTime){
    var s = performance.now();
    var currentCompletetTime = performance.now() - s;
    while(currentCompletetTime < allTime){
        currentCompletetTime = performance.now() - s;
    }
    console.log('complete--------------');
}
setTimeout(work,2000,5000);

此时我们会发现动画会在2秒时卡住,这个时候我们改造代码:

var st = null;
function work(allTime){
    clearTimeout(st);
    var unit = 1000;
    var s = performance.now();
    var currentCompletetTime = performance.now() - s;
    while(currentCompletetTime < unit && currentCompletetTime < allTime){
        currentCompletetTime = performance.now() - s;
    }
    allTime = allTime - currentCompletetTime;
    console.log(allTime)
    if(allTime > 0){
        st = setTimeout(()=>{
            work(allTime)
        },100);
    }else{
        console.log('complete--------------');
    }
}
setTimeout(work,2000,5000);

这时我们会发现随着我们手动设置unit的值越小,动画越流畅,那么有没有什么办法让浏览器自己来决定什么怎么协调呢?答案就是requestIdleCallback它可以让浏览器在每一帧的空闲时间执行代码,于是我们再次改造代码:

function workUnit(t){
    var allTime = t; 
    window.requestIdleCallback((IdleDeadline)=>{
        var s = performance.now();
        var currentCompletetTime = performance.now() - s;
        while(currentCompletetTime < allTime && IdleDeadline.timeRemaining() > 0){
            currentCompletetTime = performance.now() - s;
        }
        allTime = allTime - currentCompletetTime;
        console.log('restTime:' + allTime,'unit:' + currentCompletetTime);
        if(allTime>0){
            workUnit(allTime);
        }else{
            console.log('complete--------------');
        }
    })
}

setTimeout(workUnit,2000,5000);

这里大概列举了一个小案例,react就是在workloop时用这样的方式去中断fiber的创建,diff等过程。

以上就是react的第一个阶段,它是可以中断的,该阶段结束就会进入commit阶段,commit阶段是不可以中断的,commit阶段会将dom渲染到页面上

事件系统截图

这里截取了源码中的一些注释,有兴趣的话可以自己翻翻源码看看具体细节:

/**
   * Summary of `DOMEventPluginSystem` event handling:
   *
   *  - Top-level delegation is used to trap most native browser events. This
   *    may only occur in the main thread and is the responsibility of
   *    ReactDOMEventListener, which is injected and can therefore support
   *    pluggable event sources. This is the only work that occurs in the main
   *    thread.
   *
   *  - We normalize and de-duplicate events to account for browser quirks. This
   *    may be done in the worker thread.
   *
   *  - Forward these native events (with the associated top-level type used to
   *    trap it) to `EventPluginRegistry`, which in turn will ask plugins if they want
   *    to extract any synthetic events.
   *
   *  - The `EventPluginRegistry` will then process each event by annotating them with
   *    "dispatches", a sequence of listeners and IDs that care about that event.
   *
   *  - The `EventPluginRegistry` then dispatches the events.
   *
   * Overview of React and the event system:
   *
   * +------------+    .
   * |    DOM     |    .
   * +------------+    .
   *       |           .
   *       v           .
   * +------------+    .
   * | ReactEvent |    .
   * |  Listener  |    .
   * +------------+    .                         +-----------+
   *       |           .               +--------+|SimpleEvent|
   *       |           .               |         |Plugin     |
   * +-----|------+    .               v         +-----------+
   * |     |      |    .    +--------------+                    +------------+
   * |     +-----------.--->|PluginRegistry|                    |    Event   |
   * |            |    .    |              |     +-----------+  | Propagators|
   * | ReactEvent |    .    |              |     |TapEvent   |  |------------|
   * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|
   * |            |    .    |              |     +-----------+  |  utilities |
   * |     +-----------.--->|              |                    +------------+
   * |     |      |    .    +--------------+
   * +-----|------+    .                ^        +-----------+
   *       |           .                |        |Enter/Leave|
   *       +           .                +-------+|Plugin     |
   * +-------------+   .                         +-----------+
   * | application |   .
   * |-------------|   .
   * |             |   .
   * |             |   .
   * +-------------+   .
   *                   .
   *    React Core     .  General Purpose Event Plugin System
   */

-----------我是分割线:下面的内容大概讲了一下总体的原理,有兴趣的话可以阅读一下--------------

先上一张总体架构图

简化了很多流程,这里只取主要流程

主流程解说

  1. 从render函数起步,render函数第一个参数将接受一个vdom-tree。
  2. 通过scheduleRoot开始调度,shceduleRoot会使用到workInProgressRoot,nextUnitOfWork,currentRoot以及workInProgressRoot.alternate这几个关键元素
  3. 我们会通过requestIdleCallBack去执行wookloop(react中自己实现了该函数的polyfill,这里不会讲述)
  4. 通过! deadline.timeRemaining() < 1 && nextUnitOfWork,判断时间片是否还有剩余并且nextUnitOfWork任务还有剩余去循环执行performUnitOfWork(nextUnitOfWork),该函数的作用是生成fiber-tree以及收集更新时的依赖。
  5. 以上调度过程就是可以被打断的,当没有nextUnitOfWork时,将调用commitRoot更新页面

wookloop的流程详解

基于我发的整体架构图

  1. workLoop函数会首先判断是否存在任务,并且该时间片是否使用完(在此我们可以先不考虑时间片使用完成的情况),当任务存在,会调用performUnitOfWork,该函数会立即调用beginWork对当前fiberNode即nextUnitOfWork的子节点进行调和,生成新的fiber-tree。(这里只是部分fiber-tree形成)
  2. 当该节点调和完成,react会将currentFiber指向他的child,并将该节点return出去赋值给nextUnitOfWork再次调用wookLoop直到child为空。
  3. 此时从该节点开始收集依赖,(注意fiber-tree并没有完全形成),调用completeUnitOfWork(currentFiber)开始收集依赖,当该fiber-node依赖收集完成,react将该节点移向他的sibling节点,并将该节点赋值给nextUnitOfWork,再次调用performUnitOfWork,此时会生成该节点的fiber-tree,并且由于sibling节点没有child,将再次调用completeUnitOfWork收集该节点依赖。
  4. 在第3步中,如果sibling节点没有,react将会移动当前节点到到其父节点,再次通过completeUnitOfWork收集父节点依赖。
  5. 重复3,4过程直到当前节点为空,此时fiber-tree形成完成,依赖收集完成。
  6. 记住一点以上过程是通过requestIdleCallBack执行的,那么以上过程便可以被打断。

如何调和?

先上图片

流程解说

  1. 首先根据是否存在oldFiber来判断是否是第一次渲染,如果是接下来的判断都属于新增节点,如果不是那么会更具新老节点的diff比对形成新的fiber-tree,
  2. 比对时我们会顺序遍历新老节点,(源码中会根据key形成map数据结构来比对节点)这里的比对方式和官网给出的说法是一致的,这里主要说的使一些性能上的优化,当react判断是第一次更新(不是第一次渲染),他会复用oldFiber,如果是第二次或者更多次更新,react会复用用缓存在alternate节点上的老节点,从而生成新的fiber-tree
  3. 调和的目的就是形成新的fiber-tree

收集依赖?

再来看一张图片

  1. 更具以上分析当开始调用completeUnitOfWork收集依赖时,该节点是最后一个child元素,从该元素开始,收集该元素的effect及其child的effect(最后一个元素无child)(整张图就是这个意思)
  2. 按照本身--->兄弟节点---->父节点的顺序向上遍历,直到rootFiber节点所有依赖收集完毕。(根据整个架构图推出)

提交fiberRoot

上图:

  1. 当收集完依赖,我们开始执行commitRoot,首先我们删除需要删除的节点,接着会从fistEffect开始遍历更新页面dom节点
  2. 调用commitWork(currentFiber)更新此fiber的dom节点,首先我们会寻找到该fiber节点的第一个父容器dom节点,在更具该节点effectTag来更新、删除或者除添加节点。
  3. 将effect指向下一个重读2过程,直到所有effect遍历完成。