Skip to content

整体流程图(TODO)

Image


1. HostRoot & HostComponent举例分析

当我们使用下面的测试代码时,涉及到有

  • HostRoot:根类型,代表的是#root
  • HostComponent:原生标签类型,代表的是<div><span><p>
javascript
const domNode = document.getElementById('root');
const root = ReactDOM.createRoot(domNode);
root.render(
        <div>
          <span>
            <p></p>
          </span>
        </div>
);

1.1 HostRoot-beginWork

触发的是updateHostRoot()方法,直接触发的就是reconcileChildren()

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
  }
}

function updateHostRoot(current, workInProgress, renderLanes) {
  if (nextChildren === prevChildren) {
    // 新旧children相同,阻止渲染
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

而我们知道,reconcileChildren()实际调用的就是ChildReconciler(true)或者是ChildReconciler(false),而这里由于current不为空,因此用的是ChildReconciler(true)

javascript
var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes
    );
  } else {
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes
    );
  }
}

那为什么current不为空呢?

从下面performUnitOfWork()可以知道,current就是fiber.alternate,而根fiber的早期的准备中,就已经触发createWorkInProgress()进行fiber.alternate的构建(只有根fiber才有如此待遇!)

javascript
function performUnitOfWork(unitOfWork) {
  var current = unitOfWork.alternate;
  var next = beginWork(current, unitOfWork, subtreeRenderLanes);
  //...
}
function createWorkInProgress(current, pendingProps) {
  var workInProgress = current.alternate;

  if (workInProgress === null) {
    workInProgress = createFiber(...);

    workInProgress.alternate = current;
    current.alternate = workInProgress;
  }
}

回到ChildReconciler(true),此时

  • returnFiber: current.alternate双缓冲树的根fiber
  • currentFirstChild:旧的children第一个元素,此时由于是首次渲染,因此为null
  • newChild:新的children,如下面所示,触发的是placeSingleChild(reconcileSingleElement())方法
javascript
child = {
  $$typeof: Symbol(react.element),
  key: null,
  props: { children: {...} },
  ref: null,
  type: "div",
  _owner: null,
};
javascript
function ChildReconciler(shouldTrackSideEffects) {
  function reconcileChildFibers(returnFiber,currentFirstChild,newChild...) {
    if (typeof newChild === "object" && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes
            )
          );
      }
    }
  }
  return reconcileChildFibers;
}

1.1.1 reconcileSingleElement

直接根据上面child代码所展示的数据触发createFiberFromElement()进行fiber的构建,然后直接返回构建的fiber

javascript
function reconcileSingleElement(
  returnFiber,
  currentFirstChild,
  element,
  lanes
) {
  if (element.type === REACT_FRAGMENT_TYPE) {
    //...
  } else {
    var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);

    _created4.ref = coerceRef(returnFiber, currentFirstChild, element);
    _created4.return = returnFiber;
    return _created4;
  }
}

注意:此时element,也就是上面的childprops就是它的children数据,比如<span>、比如<p>,会直接放在fiber.pendingProps(如下面代码所示),这个fiber.pendingProps后续流程中会用到

javascript
function createFiberFromElement(element, mode, lanes) {
  var owner = null;

  var type = element.type;
  var key = element.key;
  var pendingProps = element.props;
  var fiber = createFiberFromTypeAndProps(
    type,
    key,
    pendingProps,
    owner,
    mode,
    lanes,
  );

  return fiber;
}

1.1.2 placeSingleChild

此时的newFiber是根fiberchildFiber,也就是<div>所代表的fiber,此时由于

  • shouldTrackSideEffects=true
  • newFiber.alternate=null

因此newFiber打上了Placementflags标签

javascript
function placeSingleChild(newFiber) {
  if (shouldTrackSideEffects && newFiber.alternate === null) {
    newFiber.flags |= Placement;
  }

  return newFiber;
}

1.1.3 总结

回到performUnitOfWork(),此时beginWork()返回的就是<div>所对应的fiber,然后触发workInProgress=next,然后继续执行beginWork(),此时fiber.tagHostComponent

javascript
function performUnitOfWork(unitOfWork) {
  var current = unitOfWork.alternate;
  var next;
  //...
  next = beginWork(current, unitOfWork, subtreeRenderLanes);

  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
  //...
}

1.2 HostComponent-beginWork

触发的是updateHostComponent()方法

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
  }
}

fiber.pendingProps中获取对应的children数据

7.1.1 placeSingChild中可以查看fiber.pendingProps具体的分析

然后触发

  • markRef():如果存在fiber.ref,则标记Refflags标签
  • reconcileChildren():在首次渲染中,不协调子节点,这里直接构建fiber.childFiber
  • 直接返回构建的fiber.childFiber
javascript
function updateHostComponent(current, workInProgress, renderLanes) {
  var type = workInProgress.type;
  var nextProps = workInProgress.pendingProps;
  var prevProps = current !== null ? current.memoizedProps : null;
  var nextChildren = nextProps.children;

  markRef(current, workInProgress);
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}
function markRef(current, workInProgress) {
  var ref = workInProgress.ref;
  if (
    (current === null && ref !== null) ||
    (current !== null && current.ref !== ref)
  ) {
    workInProgress.flags |= Ref;
    workInProgress.flags |= RefStatic;
  }
}

由于HostComponent不会跟HostRoot类型一样先创建fiber.alternate,因此这里current=null,触发的是ChildReconciler(false)

javascript
var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes
    );
  } else {
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes
    );
  }
}

此时nextChildren变为type: span,但是其它属性跟<div>一样

javascript
nextChildren = {
  $$typeof: Symbol(react.element),
  key: null,
  props: { children: {...} },
  ref: null,
  type: "span",
  _owner: null,
};

因此触发的也是同样的placeSingleChild(reconcileSingleElement())方法

javascript
function ChildReconciler(shouldTrackSideEffects) {
  function reconcileChildFibers(returnFiber,currentFirstChild,newChild...) {
    if (typeof newChild === "object" && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes
            )
          );
      }
    }
  }
  return reconcileChildFibers;
}

只不过相比较HostRoot触发reconcileChildren(),这里的shouldTrackSideEffects=false,因此不会打上Placementflags标签,直接返回fiber,什么都不操作

reconcileChildren()返回的是<span>新构建的fiber,此时workInProgress=<span>,然后又经历一遍HostComponent-beginWork的流程,最终<p>reconcileChildren()由于传入的childnull,因此直接返回null

javascript
const domNode = document.getElementById('root');
const root = ReactDOM.createRoot(domNode);
root.render(
    <div>
        <span>
            <p></p>
        </span>
    </div>
);

如下面代码所示,此时next=null,触发completeUnitOfWork(),此时unitOfWork=<span>对应的fiber

javascript
function workLoopSync() {
  while (workInProgress !== null) {
    var current = unitOfWork.alternate;
    var next;
    //...
    next = beginWork(current, unitOfWork, subtreeRenderLanes);

    unitOfWork.memoizedProps = unitOfWork.pendingProps;

    if (next === null) {
      // If this doesn't spawn new work, complete the current work.
      completeUnitOfWork(unitOfWork);
    } else {
      workInProgress = next;
    }
    //...
  }
}

1.3 HostComponent-completeWork

对于HostComponent,由于在beginWork()只是创建了fiber,并没有创建对应的DOM元素,因此workInProgress.stateNode=null

从下面代码,可以知道,整体核心流程可以总结为:

  • createInstance():使用原生createElement()方法创建原生DOM
  • appendAllChildren():原生DOM之间的关联parentInstance.appendChild(child)
  • workInProgress.stateNode = instance:将原生DOM赋值给fiber.stateNode
  • finalizeInitialChildren():初始化原生元素的一些事件处理和初始化属性,比如input输入框的input.value等等
  • bubbleProperties()进行flagslanes的冒泡:处理fiber.subtreeFlagsfiber.childLanes进行flagslanes的冒泡合并
javascript
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case HostComponent: {
      var type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        //...更新逻辑
      } else {
        if (!newProps) {
          bubbleProperties(workInProgress);
          return null;
        }
        var instance = createInstance(type, ...);
        appendAllChildren(instance, workInProgress, false, false);
        workInProgress.stateNode = instance;
        
        if (finalizeInitialChildren(...)) {
          //... workInProgress.flags |= Update;
        }
        if (workInProgress.ref !== null) {
          //...workInProgress.flags |= Ref;
        }
      }
      bubbleProperties(workInProgress);
      return null;
    }
  }
}

1.3.1 appendAllChildren()

从下面代码可以看出,有非常多层逻辑,涉及到多种fiber.tag,以及各种childsibling的寻找,这其实涉及到fiber.tag=Fragment等多层级嵌套DOM

的寻找,比如<><><p></p></></>p标签需要不断向上寻找多层触发dom.appendChild(),这种情况将在后续进行分析

在我们这个示例中,我们只命中了node.tag === HostComponent,因此触发了

  • <span></span>触发appendChild(<p></p>),变为<span><p></p></span>
  • <div></div>触发appendChild(),变为<div><span><p></p></span></div>
javascript
appendAllChildren = function (parent, workInProgress) {
  var node = workInProgress.child;

  while (node !== null) {
    if (node.tag === HostComponent || node.tag === HostText) {
      appendInitialChild(parent, node.stateNode);
    } else if (node.tag === HostPortal) {
    } else if (node.child !== null) {
      node.child.return = node;
      node = node.child;
      continue;
    }

    if (node === workInProgress) {
      return;
    }

    while (node.sibling === null) {
      if (node.return === null || node.return === workInProgress) {
        return;
      }
      node = node.return;
    }

    node.sibling.return = node.return;
    node = node.sibling;
  }
};
function appendInitialChild(parentInstance, child) {
    parentInstance.appendChild(child);
}

打印出来的数据结构如下图所示

1.3.2 bubbleProperties()

childrenFiber相关的lanesflags向上冒泡

javascript
function bubbleProperties(completedWork) {
  var didBailout =
    completedWork.alternate !== null &&
    completedWork.alternate.child === completedWork.child;
  var newChildLanes = NoLanes;
  var subtreeFlags = NoFlags;
  //...
  if (!didBailout) {
    var _child = completedWork.child;
    while (_child !== null) {
      newChildLanes = mergeLanes(
        newChildLanes,
        mergeLanes(_child.lanes, _child.childLanes)
      );
      subtreeFlags |= _child.subtreeFlags;
      subtreeFlags |= _child.flags;
      
      _child.return = completedWork;
      _child = _child.sibling;
    }
    completedWork.subtreeFlags |= subtreeFlags;
  } else {
    //...
  }
  completedWork.childLanes = newChildLanes;
  return didBailout;
}

1.4 HostRoot-completeWork

由于我们初始化时hydrated=false,因此这里进行Snapshot标记

然后触发bubbleProperties(),将childrenFiber相关的lanesflags向上冒泡

javascript
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case HostRoot: {
      if (current !== null) {
        var prevState = current.memoizedState;
        if (
                !prevState.isDehydrated ||
                (workInProgress.flags & ForceClientRender) !== NoFlags
        ) {
          workInProgress.flags |= Snapshot;
        }
      }
      //...
      bubbleProperties(workInProgress);
      return null;
    }
  }
}

1.5 commit阶段

在上面对于commit阶段的分析中,我们知道会先处理children->children.sibling->parent->finishedWork

这里不再重复这个流程的代码逻辑,直接展示对应的执行方法

1.5.1 HostComponent-commitBeforeMutationEffects()

Snapshot标记,不处理

1.5.2 HostRoot-commitBeforeMutationEffects()

主要处理Snapshot标记,我们知道在completeWork()进行Snapshot标记,因此这里会触发clearContainer()进行dom.textContent的重置,会清空#root的所有children数据

比如<div id="divA">This is <span>some</span> text!</div>,你可以用textContent 去获取该元素的文本内容:This is some text!

在节点上设置 textContent 属性的话,会删除它的所有子节点,并替换为一个具有给定值的文本节点(可以线上运行试试效果)

javascript
function commitBeforeMutationEffectsOnFiber(finishedWork) {
  var current = finishedWork.alternate;
  var flags = finishedWork.flags;

  if ((flags & Snapshot) !== NoFlags) {
    switch (finishedWork.tag) {
      case HostRoot: {
        var root = finishedWork.stateNode;
        clearContainer(root.containerInfo);
        break;
      }
    }
  }
}
function clearContainer(container) {
  if (container.nodeType === ELEMENT_NODE) {
    container.textContent = "";
  } else if (container.nodeType === DOCUMENT_NODE) {
    //...
  }
}

1.5.3 HostRoot-commitMutationEffects()

没有Update相关标记需要处理

从下面代码,我们知道,总体逻辑为:

  • 先触发recursivelyTraverseMutationEffects()处理children,由于HostRoot-beginWork中触发reconcileChildren()shouldTrackSideEffects=true,因此对于HostRoot的第一层children会进行Placement的标记 => 在下面小节将展开分析
  • commitReconciliationEffects()HostRoot没有Placement的标记,不做任何处理
javascript
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
  var current = finishedWork.alternate;
  var flags = finishedWork.flags;
  switch (finishedWork.tag) {
    case HostRoot: {
      recursivelyTraverseMutationEffects(root, finishedWork);
      commitReconciliationEffects(finishedWork);
      if (flags & Update) {
        //...
      }
      return;
    }
  }
}
function commitReconciliationEffects(finishedWork) {
  var flags = finishedWork.flags;
  if (flags & Placement) {
    commitPlacement(finishedWork);
    finishedWork.flags &= ~Placement;
  }
  if (flags & Hydrating) {
    finishedWork.flags &= ~Hydrating;
  }
}

1.5.4 HostComponent-commitMutationEffects()

除了由于HostRoot的第一层children触发reconcileChildren()shouldTrackSideEffects=true,其他的时候触发reconcileChildren()shouldTrackSideEffects=false,因此其它层级触发commitMutationEffectsOnFiber()时:

  • recursivelyTraverseMutationEffects()parentFiber.subtreeFlags & MutationMask不符合,不触发深度继续遍历
  • commitReconciliationEffects():没有Placement的标记,不做任何处理
javascript
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
  var current = finishedWork.alternate;
  var flags = finishedWork.flags;
  switch (finishedWork.tag) {
    case HostComponent: {
      recursivelyTraverseMutationEffects(root, finishedWork);
      commitReconciliationEffects(finishedWork);
      if (flags & Ref) {
        //...
      }
      if (flags & Update) {
        //...
      }
      return;
    }
  }
}

接下来我们着重分析能够触发commitReconciliationEffects()的第一层级

由于HostRoot的第一层children触发reconcileChildren()shouldTrackSideEffects=true,因此会标记Placement,从而触发commitPlacement()

注意commitPlacement()判断是parentFiber.tag!!!不是fiber.tag

  • getHostParentFiber():获取当前fiber的父级fiberfiber.tag=HostComponent/HostRoot/HostPortal)
  • getHostSibling():获取当前fiberDOM兄弟节点
  • insertOrAppendPlacementNodeIntoContainer():根据实际位置情况(可能有多层级嵌套)进行
    • parentDom.appendChild(currentFiber.stateNode)添加到末尾
    • 或者
    • parentDom.insertBefore(currentFiber.stateNode,当前fiber的DOM兄弟节点)在某个位置前插入DOM

getHostSibling()insertOrAppendPlacementNodeIntoContainer()的逻辑较为复杂,后续再做分析

javascript
function commitReconciliationEffects(finishedWork) {
  var flags = finishedWork.flags;
  if (flags & Placement) {
    commitPlacement(finishedWork);
    finishedWork.flags &= ~Placement;
  }
  if (flags & Hydrating) {
    finishedWork.flags &= ~Hydrating;
  }
}
function commitPlacement(finishedWork) {
  var parentFiber = getHostParentFiber(finishedWork);
  switch (parentFiber.tag) {
    case HostRoot:
    case HostPortal: {
      var _parent = parentFiber.stateNode.containerInfo;
      var _before = getHostSibling(finishedWork);
      insertOrAppendPlacementNodeIntoContainer(
              finishedWork,
              _before,
              _parent,
      );
      break;
    }
  }
}

在我们这个示例中,就是触发#root.appendChild(<div></div>),而我们在HostComponent-completeWork()中已经形成了<div><span><p></p></span></div>,因此整个DOM树就此构建完成!

1.5.5 commitLayoutEffects()

处理LayoutMask,也就是Update | Callback | Ref | Visibility相关flags标记,本次示例中,没有相关相关flags标记,因此不做任何处理

javascript
function commitLayoutEffectOnFiber(
        finishedRoot,
        current,
        finishedWork,
        committedLanes
) {
  // var LayoutMask = Update | Callback | Ref | Visibility;
  if ((finishedWork.flags & LayoutMask) !== NoFlags) {
    switch (finishedWork.tag) {
      case HostComponent:
            //...
    }
  }
}

2. HostText举例分析

当我们使用下面的测试代码时,涉及到有

  • HostRoot:根类型,代表的是#root
  • HostComponent:原生标签类型,代表的是<div><span>
  • HostText:文本类型,代表的是Child2
javascript
const domNode = document.getElementById("root");
const root = ReactDOM.createRoot(domNode);
root.render(
    <div id="parent">
      <span>我是Child1</span>
      Child2
    </div>
);

2.1 HostRoot-beginWork

触发的是updateHostRoot()方法,直接触发的就是reconcileChildren()

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
  }
}
function updateHostRoot(current, workInProgress, renderLanes) {
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

与上面7.1 HostRoot-beginWork的流程一致,都只有一个HostComponent: <div>,因此触发

  • reconcileSingleElement():创建fiber数据
  • placeSingleChild():打上Placement标记

然后beginWork深度遍历,继续往下执行,目前fiber切换为<div></div>

2.2 (div)HostComponent-beginWork

7.HostRoot&HostComponent举例分析不同的是,此时的fiber<div>对应的fiber,但是它的newChild是一个数组,也就是

  • <span>我是Child1</span>
  • Child2

因此触发的是reconcileChildrenArray()!而不是reconcileSingleElement()

javascript
function ChildReconciler(shouldTrackSideEffects) {
  function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
    if (typeof newChild === "object" && newChild !== null) {
      if (isArray(newChild)) {
        return reconcileChildrenArray(
                returnFiber,
                currentFirstChild,
                newChild,
                lanes
        );
      }
    }
  }
  return reconcileChildFibers;
}

这里reconcileChildrenArray()涉及到比较复杂的diff逻辑,将在后续文章中详细展开,这里只展示我们示例所触发的代码逻辑

直接遍历newChild,调用createChild()创建Fiber,然后触发placeChild()打上Forked标记

HostRoot的第一层child所执行的shouldTrackSideEffects=true,其他层都是false,因此下面代码触发newFiber.flags |= Forked

javascript
function reconcileChildrenArray() {
  var oldFiber = currentFirstChild;
  if (oldFiber === null) {
    for (; newIdx < newChildren.length; newIdx++) {
      var _newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
      lastPlacedIndex = placeChild(_newFiber, lastPlacedIndex, newIdx);
      if(_newFiber === null) {
        continue;
      }
      if (previousNewFiber === null) {
        resultingFirstChild = _newFiber;
      } else {
        previousNewFiber.sibling = _newFiber;
      }
      previousNewFiber = _newFiber;
    }
    return resultingFirstChild;
  }
}
function placeChild(newFiber, lastPlacedIndex, newIndex) {
  newFiber.index = newIndex;
  if (!shouldTrackSideEffects) {
    newFiber.flags |= Forked;
    return lastPlacedIndex;
  }
  //...
}

2.2.1 createChild-createFiberFromText

这里的创建fiber涉及到多种类型的创建,代码也非常多,这里只展示示例所触发的代码逻辑

我们知道示例的数组是:

  • <span>我是Child1</span>
  • Child2

当涉及到<span></span>对应的fiber创建时,如同7.1.1 reconcileSingleElement()的逻辑一致,触发的是createFiberFromElement()单元素fiber创建,这里不再重复分析

而涉及到Child2时会触发createFiberFromText()创建fiber

javascript
function createChild(returnFiber, newChild, lanes) {
  if (
          (typeof newChild === "string" && newChild !== "") ||
          typeof newChild === "number"
  ) {
    var created = createFiberFromText("" + newChild, returnFiber.mode, lanes);
    created.return = returnFiber;
    return created;
  }

  if (typeof newChild === "object" && newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE: {
        var _created = createFiberFromElement(
                newChild,
                returnFiber.mode,
                lanes
        );

        _created.ref = coerceRef(returnFiber, null, newChild);
        _created.return = returnFiber;
        return _created;
      }
    }
  }
  return null;
}

而从下面代码我们可以知道,此时的pendingProps=content="Child2",也就是说pendingProps不一定就是object,也有可能是字符串!

javascript
function createFiberFromText(content, mode, lanes) {
  var fiber = createFiber(HostText, content, null, mode);
  fiber.lanes = lanes;
  return fiber;
}
var createFiber = function (tag, pendingProps, key, mode) {
  return new FiberNode(tag, pendingProps, key, mode);
}

2.3 (span)HostComponent-beginWork

跟上面流程相似,触发updateComponent()->reconcileChildren()->mountChildFibers()也就是ChildReconciler(false)

由于此时的newChild=我是Child1,是一个纯文本,因此会触发

javascript
function ChildReconciler(shouldTrackSideEffects) {
  function reconcileChildFibers(returnFiber, currentFirstChild, newChild, ...) {
    //...
    if (
            (typeof newChild === "string" && newChild !== "") ||
            typeof newChild === "number"
    ) {
      return placeSingleChild(
              reconcileSingleTextNode(
                      parentFiber,
                      oldFiberFirstChild,
                      "" + newChild,
                      lanes
              )
      );
    }
    //...
  }
  return reconcileChildFibers;
}

reconcileSingleTextNode()的逻辑也非常简单,就是创建一个文本元素

javascript
function reconcileSingleTextNode(
        returnFiber: Fiber,
        currentFirstChild: Fiber | null,
        textContent: string,
        lanes: Lanes
): Fiber {
  //...省略更新相关逻辑
  const created = createFiberFromText(textContent, returnFiber.mode, lanes);
  created.return = returnFiber;
  return created;
}

placeSingleChild(reconcileSingleTextNode())方法,进行文本元素的创建后,不会打上Placement标记(第一层才会打上)

由于<span>是叶子结点,因此执行完beginWork(),就会执行completeWork()

2.4 (纯文本)HostText-beginWork

无任何处理,直接返回null

由于纯文本是叶子结点,因此执行完beginWork(),就会执行completeWork()

2.5 (纯文本)HostText-completeWork

直接使用当前文本进行document.createTextNode()创建对应的文本DOM,然后赋值给workProgress.stateNode,最后再触发bubbleProperties(),将childrenFiber相关的lanesflags向上冒泡

javascript
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case HostText: {
      workInProgress.stateNode = createTextInstance(
              newText,
              _rootContainerInstance,
              _currentHostContext,
              workInProgress
      );
      bubbleProperties(workInProgress);
      return null;
    }
  }
}
function createTextInstance() {
  var textNode = createTextNode(text, rootContainerInstance);
  return textNode;
}
function createTextNode(text, rootContainerElement) {
  return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
          text
  );
}

2.6 (span)HostComponent-completeWork

由于是叶子结点,因此与7.3 HostComponent-completeWork相比较,会少掉appendAllChildren()的逻辑,也就是:

  • createInstance():使用原生createElement()方法创建原生DOM
  • workInProgress.stateNode = instance:将原生DOM赋值给fiber.stateNode
  • finalizeInitialChildren():初始化原生元素的一些事件处理和初始化属性,比如input输入框的input.value等等
  • bubbleProperties()进行flagslanes的冒泡:处理fiber.subtreeFlagsfiber.childLanes进行flagslanes的冒泡合并

省略Child2纯文本fiber对应的beginWork()completeWork()分析,跟上面流程一致,beginWork()返回nullcompleteWork()创建文本元素


2.7 (div)HostComponent-completeWork

7.3 HostComponent-completeWork的流程一致:

  • createInstance():使用原生createElement()方法创建原生DOM
  • appendAllChildren():原生DOM之间的关联parentInstance.appendChild(child),将所有childdom都关联起来
  • workInProgress.stateNode = instance:将原生DOM赋值给fiber.stateNode
  • finalizeInitialChildren():初始化原生元素的一些事件处理和初始化属性,比如input输入框的input.value等等
  • bubbleProperties()进行flagslanes的冒泡:处理fiber.subtreeFlagsfiber.childLanes进行flagslanes的冒泡合并

2.8 HostRoot-completeWork

7.4 HostRoot-completeWork流程一致

由于我们初始化时hydrated=false,因此这里进行Snapshot标记

然后触发bubbleProperties(),将childrenFiber相关的lanesflags向上冒泡

javascript
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case HostRoot: {
      if (current !== null) {
        var prevState = current.memoizedState;
        if (
                !prevState.isDehydrated ||
                (workInProgress.flags & ForceClientRender) !== NoFlags
        ) {
          workInProgress.flags |= Snapshot;
        }
      }
      //...
      bubbleProperties(workInProgress);
      return null;
    }
  }
}

2.9 commit阶段

commit阶段中,主要是根据不同flags去触发不同逻辑

由于在render阶段HostText类型并没有什么特殊flags,因此流程与7.5 commit阶段一致:

  • HostRoot: 由于Snapshot,在commitBeforeMutationEffects()触发container.textContent = ""
  • HostComponet(div):由于Placement,再commitMutationEffects()触发了commitPlacement()#root<div>两个dom进行关联

3. Fragment举例分析

经过上面HostRootHostComponentHostText的举例分析,我们已经了解到render阶段中的beginWork()completeWork()以及commit阶段中的commitBeforeMutaioncommitMutationEffectscommitLayoutEffects的具体执行逻辑,因此我们在这个Fragment中不会再进行具体的例子分析,而是直接根据这几个阶段去分析Fragment有何特殊的地方

javascript
const domNode = document.getElementById('root');
const root = ReactDOM.createRoot(domNode);
const fragment = (
        <React.Fragment>
          <React.Fragment>
            <span>我是Fragment里面的Child1</span>
          </React.Fragment>
          <p>Child2</p>
        </React.Fragment>
)
root.render(fragment);

3.1 beginWork()

3.1.1 HostRoot

一开始触发HostRootbeginWork()->reconcileChildren()

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
  }
}
function updateHostRoot(current, workInProgress, renderLanes) {
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

reconcileChildFibers()中,由于我们的第一层元素是<React.Fragment>,因此newChild.type === REACT_FRAGMENT_TYPE,因此isUnkeyedTopLevelFragment=true,因此newChild=newChild.props.children,从而触发reconcileChildrenArray()方法,此时newChild:

  • <React.Fragment></React.Fragment>
  • <p></p>

注意:也就是第一层React.Fragment直接不渲染

javascript
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
  var isUnkeyedTopLevelFragment =
          typeof newChild === "object" &&
          newChild !== null &&
          newChild.type === REACT_FRAGMENT_TYPE &&
          newChild.key === null;

  if (isUnkeyedTopLevelFragment) {
    newChild = newChild.props.children;
  }
  if (typeof newChild === "object" && newChild !== null) {
    if (isArray(newChild)) {
      return reconcileChildrenArray(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes
      );
    }
  }
}

reconcileChildrenArray()进行fiber创建调用的也是

  • createChild()->createFiberFromElement():创建对应的fiber数据
  • placeChild():打上Placement标记

注意:由于第一层是React.Fragment,它直接不渲染,直接newChild=newChild.props.children,因此此时shouldTrackSideEffects=true,会打上Placement标记

javascript
function reconcileChildrenArray() {
  var oldFiber = currentFirstChild;
  if (oldFiber === null) {
    for (; newIdx < newChildren.length; newIdx++) {
      var _newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
      lastPlacedIndex = placeChild(_newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = _newFiber;
      } else {
        previousNewFiber.sibling = _newFiber;
      }
      previousNewFiber = _newFiber;
    }
    return resultingFirstChild;
  }
}
function placeChild(newFiber, lastPlacedIndex, newIndex) {
  newFiber.index = newIndex;
  if (!shouldTrackSideEffects) {
    //...
  }
  var current = newFiber.alternate;
  if (current !== null) {
    //...
  } else {
    // This is an insertion.
    newFiber.flags |= Placement;
    return lastPlacedIndex;
  }
}

3.1.2 Fragment

此时触发的是第二个<React.Fragment>beginWork()!

触发updateFragment(),然后直接触发reconcileChildren()进行childrenfiber构建

注:reconcileChildren()就是创建第二个<React.Fragment>children对应的fiber,没什么特别的地方,这里就不展开分析了!

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case Fragment:
      return updateFragment(current, workInProgress, renderLanes);
  }
}
function updateFragment(current, workInProgress, renderLanes) {
  var nextChildren = workInProgress.pendingProps;
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

3.2 completeWork()

没有进行什么特殊的处理,只是触发bubbleProperties()

javascript
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      bubbleProperties(workInProgress);
      return null;
  }
}

3.3 commit阶段

由于在beginWork()阶段,直接略过了第一层<React.Fragment>fiber创建,因此目前rootchildrenFiber如下图所示

  • <React.Fragment>
  • <p>

Image

因此触发commit阶段时,我们可以直接忽视第一层<React.Fragment>,直接看第二层<React.Fragment>

commitBeforeMutaion中,触发了HostRootclearContainerContent()Fragment无任何处理

commitMutationEffects中,触发了default分支,也是触发

  • recursivelyTraverseMutationEffects()
  • commitReconciliationEffects()
javascript
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
  var current = finishedWork.alternate;
  var flags = finishedWork.flags;
  switch (finishedWork.tag) {
    default: {
      recursivelyTraverseMutationEffects(root, finishedWork);
      commitReconciliationEffects(finishedWork);
      return;
    }
  }
}

recursivelyTraverseMutationEffects()就是继续向下遍历HostComponent,由于shouldTrackSideEffects=false,因此不会打上flags,因此不会触发commit阶段

此时finishedWork=<React.Fragment>,会触发commitPlacement(),因此HostRoot的第一层children会打上Placement标记,然后进行dom.appendChild()操作,实现#rootchildFiber.stateNode的关联!!

javascript
function commitReconciliationEffects(finishedWork) {
  var flags = finishedWork.flags;
  if (flags & Placement) {
    commitPlacement(finishedWork);
    finishedWork.flags &= ~Placement;
  }
  if (flags & Hydrating) {
    finishedWork.flags &= ~Hydrating;
  }
}
function commitPlacement(finishedWork) {
  var parentFiber = getHostParentFiber(finishedWork);
  switch (parentFiber.tag) {
    case HostRoot:
    case HostPortal: {
      var _parent = parentFiber.stateNode.containerInfo;
      var _before = getHostSibling(finishedWork);
      insertOrAppendPlacementNodeIntoContainer(
              finishedWork,
              _before,
              _parent,
      );
      break;
    }
  }
}

上面的dom.appendChild()只看表层含义是非常好理解的!但是我们目前分析的Fragment类型,Fragment是不具备DOM的!因此它是怎么进行DOM关联的呢?

javascript
function getHostSibling(fiber) {
  var node = fiber;

  siblings: while (true) {
    while (node.sibling === null) {
      if (node.return === null || isHostParent(node.return)) {
        return null;
      }

      node = node.return;
    }

    node.sibling.return = node.return;
    node = node.sibling;

    while (
            node.tag !== HostComponent &&
            node.tag !== HostText &&
            node.tag !== DehydratedFragment
            ) {
      if (node.flags & Placement) {
        continue siblings;
      }

      if (node.child === null || node.tag === HostPortal) {
        continue siblings;
      } else {
        node.child.return = node;
        node = node.child;
      }
    }

    if (!(node.flags & Placement)) {
      return node.stateNode;
    }
  }
}

getHostSibling()中,有几条规则:

  • 我们会试着找当前fibersibling,此时的<React.Fragment>sibling=<p></p>,当然,如果node.sibling不存在,那么我们会试着往上再找一层,然后找它的sibling,因为可能存在下面的情况,当我们的fiber=<p>时,它的sibling为空,我们只能找它的return.sibling

如果当前fibersibling为空,往上一层的fiber也为空 => 没必要往上找了,直接返回null

如果当前fibersibling为空,往上一层fiber又具备DOM,并不是下面这种<><p></p></>,那我们也不能拿上一层fibersibling作为参照物去插入DOM!=> 没必要往上找了,直接返回null

html
<div>
  <React.Fragment>
    <p></p>
  </React.Fragment>
  <span/>
</div>
  • 当我们找到了当前fibersibling,我们还要判断它是不是具备DOMfiber类型,比如Fragmentfiber类型是不具备DOM的,但是HostComponentfiber类型是具备DOM
    • 如果它不具备DOM,我们要试一下它的child,比如上面示例中,我们的<React.Fragment>不具备DOM,但是它的<p>是有DOM
    • 如果它不具备DOMchild又为空 => 继续找它的下一个sibling
    • 如果它不具备DOM,本身又有Placement标记(本身就可能是插入或者移动),那我们就不能把它当作参照物,那还是得 => 继续找它的下一个sibling
    • 如果它具备DOM,那它就很可能是我们需要的参照物,判断下是否有Placement标记(本身就可能是插入或者移动),有Placement标记,那还是得 => 继续找它的下一个sibling
    • 如果它具备DOM,没有Placement标记,那它就是我们想要的稳定位置的参照物!!!

在我们这个示例中:

  • 找到了当前fibersibling=<p></p>
  • 当前fibersibling具备DOM,但是它有Placement标记(本身就可能是插入或者移动),那我们就不能把它当作参照物 => 继续找它的下一个sibling
  • 它的sibling为空,往上一层fiber又具备DOM,我们不能拿上一层fibersibling作为参照物去插入DOM!=> 没必要往上找了,直接返回null

因此最终返回的_before为空,从而触发dom.appendChild()方法,而不是dom.insertBefore()方法

javascript
function commitPlacement(finishedWork) {
  var parentFiber = getHostParentFiber(finishedWork);
  switch (parentFiber.tag) {
    case HostRoot:
    case HostPortal: {
      var _parent = parentFiber.stateNode.containerInfo;
      var _before = getHostSibling(finishedWork);
      insertOrAppendPlacementNodeIntoContainer(
              finishedWork,
              _before,
              _parent,
      );
      break;
    }
  }
}

4. FunctionComponent举例分析

javascript
const domNode = document.getElementById('root');
const root = ReactDOM.createRoot(domNode);
const App = ({testProps}) => {
  return (
          <div id={"parent"} class={testProps}>
            <p id={"child"}>我是child1</p>
          </div>
  )
}
root.render(<App testProps={"app-children-wrapper"}/>);

4.1 beginWork()

4.1.1 HostRoot

一开始触发HostRootbeginWork()->reconcileChildren()

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
  }
}
function updateHostRoot(current, workInProgress, renderLanes) {
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

reconcileChildren()此时传入的nextChildren为:

javascript
nextChildren = {
  $$typeof: Symbol(react.element),
  key: null,
  props: { testProps: "app-children-wrapper" },
  ref: null,
  type: ƒ App(_ref),
  _owner: null,
};

最终触发的createChild()createFiberFromElement()创建fiber

javascript
function createChild(returnFiber, newChild, lanes) {
  if (typeof newChild === "object" && newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE: {
        var _created = createFiberFromElement(
                newChild,
                returnFiber.mode,
                lanes
        );
        _created.ref = coerceRef(returnFiber, null, newChild);
        _created.return = returnFiber;
        return _created;
      }
    }
  }
  return null;
}

由于FunctionComponent满足typeof type === "function"+ 不满足shouldConstruct(type),因此fiberTag=IndeterminateComponent

javascript
function createFiberFromTypeAndProps() {
  // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
  var fiberTag = IndeterminateComponent;
  var resolvedType = type;
  if (typeof type === "function") {
    if (shouldConstruct(type)) {
      fiberTag = ClassComponent;
    }
  }
  var fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type;
  fiber.type = resolvedType;
  fiber.lanes = lanes;
  return fiber;
}
function shouldConstruct(Component) {
  var prototype = Component.prototype;
  return !!(prototype && prototype.isReactComponent);
}

4.1.2 IndeterminateComponent

  • 触发renderWithHoos()进行FunctionComponent的渲染
  • workInProgress.flags |= PerformedWork
  • workInProgress.tag = FunctionComponent
  • 然后触发组件函数中子元素的渲染:reconcileChildren(null, workInProgress, value)

注:传入的current=null,触发ChildReconciler(false),不进行flags的标记!

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
    }
  }
}
function mountIndeterminateComponent(...) {
  value = renderWithHooks(...);
  workInProgress.flags |= PerformedWork;
  if (
          // 存在render函数() + value.$$typeof等于undefined就是Class组件
          typeof value === "object" &&
          value !== null &&
          typeof value.render === "function" &&
          value.$$typeof === undefined
  ) {
    workInProgress.tag = ClassComponent;
    //...处理Function中Class组件的逻辑
  } else {
    //...
    workInProgress.tag = FunctionComponent;
    reconcileChildren(null, workInProgress, value, renderLanes);
    return workInProgress.child;
  }
}

4.1.2.1 renderWithHooks()

核心方法就是调用Component()进行渲染,如下面注释代码所示,我们这个示例的Component()方法为自动转化的React.createElement

javascript
function renderWithHooks() {
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber$1 = workInProgress;
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;
  //   function App(_ref) {
  //     var testProps = _ref.testProps;
  //     return React.createElement(
  //       "div",
  //       { id: "parent", class: testProps },
  //       React.createElement("p", { id: "child" }, "\\u6211\\u662Fchild1")
  //     );
  //   }
  var children = Component(props, secondArg); // workInProgress.type

  renderLanes = NoLanes;
  currentlyRenderingFiber$1 = null;
  return children;
}

执行完成Component()之后,我们可以从renderWithHooks()得到的value如下所示:

javascript
value = {
  $$typeof: Symbol(react.element),
  key: null,
  props: { id: "parent", children: {...} },
  ref: null,
  type: "div",
  _owner: null,
};

继续触发beginWork()跟上面HostComponentbeginWork()的流程一模一样,这里不再重复分析

4.2 completeWork

Image

从上图的执行顺序,我们可以知道,函数组件中的HostComponent会先执行,然后逐渐向上执行

每一个HostComponent-completeWork()会执行createInstance() + appendAllChildren()不断创建DOM以及关联parentFiber.stateNodefiber.stateNode的关系

直到IndeterminateComponentcompleteWork()执行!其实也没执行什么,只是触发bubbleProperties()

javascript
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      bubbleProperties(workInProgress);
      return null;
  }
}

4.3 commit阶段

commit阶段中,主要处理flags相关逻辑,FunctionComponent并没有构建什么特殊的flags,而FunctionComponent又处于HostRoot的第一层,因此还是按照上面所分析那样,

finishedWork=FunctionComponent时,在commitReconciliationEffects()触发commitPlacement()处理,也就是根据parentFiber.tag触发了:insertOrAppendPlacementNodeIntoContainer()

javascript
function commitReconciliationEffects(finishedWork) {
  var flags = finishedWork.flags;
  if (flags & Placement) {
    commitPlacement(finishedWork);
    finishedWork.flags &= ~Placement;
  }
  if (flags & Hydrating) {
    finishedWork.flags &= ~Hydrating;
  }
}
function commitPlacement(finishedWork) {
  var parentFiber = getHostParentFiber(finishedWork);
  switch (parentFiber.tag) {
    case HostRoot:
    case HostPortal: {
      var _parent = parentFiber.stateNode.containerInfo;
      var _before = getHostSibling(finishedWork);
      insertOrAppendPlacementNodeIntoContainer(
              finishedWork,
              _before,
              _parent,
      );
      break;
    }
  }
}

4.3.1 insertOrAppendPlacementNodeIntoContainer()

与我们上面分析不同,这里的nodeFunctionComponent,它是不具备DOM的!!!因此isHost=false,触发了第三个条件的代码

  • 我们会直接取node.child,也就是FunctionComponent中顶层元素<div>,然后触发insertOrAppendPlacementNodeIntoContainer(),这个时候nodeHostComponent,具备DOM,因此可以执行插入操作,也就是#root.appendChild(<div/>)
  • 处理完node.child还不够,我们还得处理下node.child.sibling,因此可能存在着FunctionComponent的顶层元素是一个<React.Fragment>的情况,它也是一个不具备DOM的类型,我们需要#root.appendChild(Fragment的childDOM)
javascript
function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
  var tag = node.tag;
  var isHost = tag === HostComponent || tag === HostText;

  if (isHost) {
    //...
  } else if (tag === HostPortal);
  else {
    var child = node.child;

    if (child !== null) {
      insertOrAppendPlacementNodeIntoContainer(child, before, parent);
      var sibling = child.sibling;

      while (sibling !== null) {
        insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
        sibling = sibling.sibling;
      }
    }
  }
}

当然,我们的示例是因为刚刚好FunctionComponent处于HostRoot的第一层,如果不处于第一层,那么就不会打上Placement标记,那么它们是如何关联DOM的呢?

如果FunctionComponent不处于HostRoot的第一层,假设FunctionComponent在某一个<div>的下一层级,那么就会在HostComponentcompleteWork()触发appendAllChildren()逻辑

javascript
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case HostComponent: {
      var type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        //...更新逻辑
      } else {
        if (!newProps) {
          bubbleProperties(workInProgress);
          return null;
        }
        var instance = createInstance(type, ...);
        appendAllChildren(instance, workInProgress, false, false);
        workInProgress.stateNode = instance;

        if (finalizeInitialChildren(...)) {
          //... workInProgress.flags |= Update;
        }
        if (workInProgress.ref !== null) {
          //...workInProgress.flags |= Ref;
        }
      }
      bubbleProperties(workInProgress);
      return null;
    }
  }
}

appendAllChildren()中,我们也可以看到类似的逻辑

  • 检测当前node.tag是否等于HostComponent或者HostText,如果不是,则向下寻找它对应的child=> 也就是FunctionComponet的(具备DOM的)顶层child元素
  • 处理完成当前node,还要执行parent.appendChild(node.sibling)操作

上面两个步骤跟insertOrAppendPlacementNodeIntoContainer()中关联DOM的逻辑是一样的!!

javascript
appendAllChildren = function (parent, workInProgress) {
  var node = workInProgress.child;

  while (node !== null) {
    if (node.tag === HostComponent || node.tag === HostText) {
      appendInitialChild(parent, node.stateNode);
    } else if (node.tag === HostPortal) {
    } else if (node.child !== null) {
      node.child.return = node;
      node = node.child;
      continue;
    }
    if (node === workInProgress) {
      return;
    }

    while (node.sibling === null) {
      if (node.return === null || node.return === workInProgress) {
        return;
      }
      node = node.return;
    }

    node.sibling.return = node.return;
    node = node.sibling;
  }
};

5. ClassComponent举例分析

5.1 beginWork()

5.1.1 HostRoot

一开始触发HostRootbeginWork()->reconcileChildren()

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
  }
}
function updateHostRoot(current, workInProgress, renderLanes) {
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

reconcileChildren()此时传入的nextChildren为:

javascript
nextChildren = {
  $$typeof: Symbol(react.element),
  key: null,
  props: { },
  ref: null,
  type: ƒ App(_ref),
  _owner: null,
};

最终触发的createChild()createFiberFromElement()创建fiber

javascript
function createChild(returnFiber, newChild, lanes) {
  if (typeof newChild === "object" && newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE: {
        var _created = createFiberFromElement(
                newChild,
                returnFiber.mode,
                lanes
        );
        _created.ref = coerceRef(returnFiber, null, newChild);
        _created.return = returnFiber;
        return _created;
      }
    }
  }
  return null;
}

由于ClassComponent满足typeof type === "function"+ 满足shouldConstruct(type),因此fiberTag=ClassComponent

javascript
function createFiberFromTypeAndProps() {
  // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
  var fiberTag = IndeterminateComponent;
  var resolvedType = type;
  if (typeof type === "function") {
    if (shouldConstruct(type)) {
      fiberTag = ClassComponent;
    }
  }
  var fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type;
  fiber.type = resolvedType;
  fiber.lanes = lanes;
  return fiber;
}
function shouldConstruct(Component) {
  var prototype = Component.prototype;
  return !!(prototype && prototype.isReactComponent);
}

5.1.2 ClassComponent

ClassComponent类型也是直接触发updateXXXX()方法

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case ClassComponent: {
      var _Component = workInProgress.type;
      var _unresolvedProps = workInProgress.pendingProps;

      var _resolvedProps =
              workInProgress.elementType === _Component
                      ? _unresolvedProps
                      : resolveDefaultProps(_Component, _unresolvedProps);

      return updateClassComponent(
              current,
              workInProgress,
              _Component,
              _resolvedProps,
              renderLanes
      );
    }
  }
}

updateClassComponent()所执行的内容,就比其它类型的逻辑要复杂的多了!主要分为:

  • constructClassInstance():实例化ClassComponentrender()方法还没触发,只是刚刚初始化ClassComponent
  • mountClassInstance():检测生命周期以及初始化一些变量,如果有componentDidMount,则打上UpdateLayoutStatic标记(后续渲染更新再分析)
  • finishClassComponent()ClassComponent.render()执行 + reconcileChildren()渲染子fiber
javascript
function updateClassComponent() {
  var instance = workInProgress.stateNode;
  var shouldUpdate;

  if (instance === null) {
    constructClassInstance(workInProgress, Component, nextProps);
    mountClassInstance(workInProgress, Component, nextProps, renderLanes);
    shouldUpdate = true;
  }

  var nextUnitOfWork = finishClassComponent(
          current,
          workInProgress,
          Component,
          shouldUpdate,
          hasContext,
          renderLanes
  );

  return nextUnitOfWork;
}

5.1.2.1 constructClassInstance()

如下面精简代码所示,就是直接new ctor(props, context),这个ctor就是workInProgress.type

javascript
function constructClassInstance(workInProgress, ctor, props) {
  var context = {};
  var instance = new ctor(props, context);
  adoptClassInstance(workInProgress, instance);
  return instance;
}
function adoptClassInstance(workInProgress, instance) {
  instance.updater = classComponentUpdater;
  workInProgress.stateNode = instance;
}

此时构建完成的instance如下所示

javascript
instance = {
  context: {},
  props: {},
  refs: {},
}

classComponentUpdater是一组工具对象,具备多个工具方法

javascript
var classComponentUpdater = {
  isMounted: isMounted,
  enqueueSetState: function (inst, payload, callback) {
    //...
  },
  enqueueReplaceState: function (inst, payload, callback) {
    //...
  },
  enqueueForceUpdate: function (inst, callback) {
    //...
  },
};

5.1.2.2 mountClassInstance()

如下面精简代码所示,判断instance.componentDidMount是否存在,然后打上对应的flags

javascript
function mountClassInstance(workInProgress, ctor, newProps, renderLanes) {
  var instance = workInProgress.stateNode;
  instance.props = newProps;
  instance.state = workInProgress.memoizedState;
  //...
  if (typeof instance.componentDidMount === "function") {
    var fiberFlags = Update;
    fiberFlags |= LayoutStatic;
    workInProgress.flags |= fiberFlags;
  }
}

5.1.2.3 finishClassComponent()

最后触发instance.render()执行,打上PerformedWork标记,触发reconcileChildren()进行children的渲染

javascript
function finishClassComponent() {
  var instance = workInProgress.stateNode; // Rerender
  ReactCurrentOwner$1.current = workInProgress;
  var nextChildren = instance.render();
  workInProgress.flags |= PerformedWork;

  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  workInProgress.memoizedState = instance.state; // The context might have changed so we need to recalculate it.

  return workInProgress.child;
}

此时的nextChildren如下所示,跟上面分析的jsx转化后的数据没什么区别,后续就是触发HostComponent-beginWork()以及HostComponent-completeWork()进行DOM的创建和关联!

javascript
nextChildren = {
  $$typeof: Symbol(react.element),
  key: null,
  props: { id: "我是App顶层元素div", children: {...} },
  ref: null,
  type: "div",
};

5.2 completeWork()

没有执行什么方法,只是触发了bubbleProperties()

javascript
function completeWork(current, workInProgress, renderLanes) {
  switch (workInProgress.tag) {
    case ClassComponent: {
      var Component = workInProgress.type;

      if (isContextProvider(Component)) {
        popContext();
      }

      bubbleProperties(workInProgress);
      return null;
    }
  }
}

5.3 commit阶段

FunctionComponent 10.3 commit阶段的分析基本一致,触发了commitPlacement()->insertOrAppendPlacementNodeIntoContainer(),然后由于当前node不是isHost,从而向下寻找node.child进行DOM的关联

javascript
function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
  var tag = node.tag;
  var isHost = tag === HostComponent || tag === HostText;

  if (isHost) {
    //...
  } else if (tag === HostPortal);
  else {
    var child = node.child;

    if (child !== null) {
      insertOrAppendPlacementNodeIntoContainer(child, before, parent);
      var sibling = child.sibling;

      while (sibling !== null) {
        insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
        sibling = sibling.sibling;
      }
    }
  }
}

5.4 IndeterminateComponent

在我们上面关于FunctionComponent的分析中,我们发现存在一种情况!也可以判断定为ClassComponent

javascript
function beginWork(current, workInProgress, renderLanes) {
  didReceiveUpdate = false;
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
    }
  }
}
function mountIndeterminateComponent(...) {
  value = renderWithHooks(...);
  workInProgress.flags |= PerformedWork;
  if (
          // 存在render函数() + value.$$typeof等于undefined就是Class组件
          typeof value === "object" &&
          value !== null &&
          typeof value.render === "function" &&
          value.$$typeof === undefined
  ) {
    workInProgress.tag = ClassComponent;
    adoptClassInstance(workInProgress, value);
    mountClassInstance(workInProgress, Component, props, renderLanes);
    return finishClassComponent(null, workInProgress, Component, true, hasContext, renderLanes);
  } else {
    //...
    workInProgress.tag = FunctionComponent;
    reconcileChildren(null, workInProgress, value, renderLanes);
    return workInProgress.child;
  }
}

FunctionComponent返回的数据返回了render()方法,如下面所示,我们也会作为ClassComponent去处理以及渲染!!

javascript
const AppTest = ()=> {
  return {
    render() {
      <div id={"我是App顶层元素div"}>
        <span>我是内容元素span</span>
      </div>
    }
  }
}

6.(TOTD)总结

从上面的分析中,我们可以知道,当节点不是root时,我们会直接在render()阶段添加DOM元素形成HTML树,这跟build own react的描述是一样的

而节点是root时,会在最后的commit阶段才出发DOM元素的添加,这跟build own react最终再添加DOM到root是逻辑是一样的

在当前fiber的逻辑中,先触发reconcileChildren()创建当前fiber的children对应的fiber数据,然后返回第一个children,作为beginWork()的返回值,作为下一个beginWork()处理的fiber,这就是可以从parent->child->叶子child的遍历顺序

创建完成fiber后,从叶子child->child->parent开始触发completeWork()进行DOM元素的创建以及DOM.appendAllChild()的逻辑的触发

最终在commit阶段才触发root对应的dom.appendChild()

详细说明??

  • render()阶段:appendAllChildren() - build own react的performUnitOfwork()逻辑类似,不处理root元素
  • commit()阶段:处理Placement标记 - build own react的performUnitOfwork()逻辑类似,最终处理root元素

参考

  1. React中的任务饥饿行为