整体流程图(TODO)
1. HostRoot & HostComponent举例分析
当我们使用下面的测试代码时,涉及到有
HostRoot:根类型,代表的是#rootHostComponent:原生标签类型,代表的是<div>、<span>、<p>
const domNode = document.getElementById('root');
const root = ReactDOM.createRoot(domNode);
root.render(
<div>
<span>
<p></p>
</span>
</div>
);1.1 HostRoot-beginWork
触发的是updateHostRoot()方法,直接触发的就是reconcileChildren()
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)
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才有如此待遇!)
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双缓冲树的根fibercurrentFirstChild:旧的children第一个元素,此时由于是首次渲染,因此为nullnewChild:新的children,如下面所示,触发的是placeSingleChild(reconcileSingleElement())方法
child = {
$$typeof: Symbol(react.element),
key: null,
props: { children: {...} },
ref: null,
type: "div",
_owner: null,
};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
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,也就是上面的child的props就是它的children数据,比如<span>、比如<p>,会直接放在fiber.pendingProps(如下面代码所示),这个fiber.pendingProps后续流程中会用到
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是根fiber的childFiber,也就是<div>所代表的fiber,此时由于
shouldTrackSideEffects=truenewFiber.alternate=null
因此newFiber打上了Placement的flags标签
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}1.1.3 总结
回到performUnitOfWork(),此时beginWork()返回的就是<div>所对应的fiber,然后触发workInProgress=next,然后继续执行beginWork(),此时fiber.tag为HostComponent
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()方法
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,则标记Ref的flags标签reconcileChildren():在首次渲染中,不协调子节点,这里直接构建fiber.childFiber- 直接返回构建的
fiber.childFiber
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)
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>一样
nextChildren = {
$$typeof: Symbol(react.element),
key: null,
props: { children: {...} },
ref: null,
type: "span",
_owner: null,
};因此触发的也是同样的placeSingleChild(reconcileSingleElement())方法
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,因此不会打上Placement的flags标签,直接返回fiber,什么都不操作
reconcileChildren()返回的是<span>新构建的fiber,此时workInProgress=<span>,然后又经历一遍HostComponent-beginWork的流程,最终<p>的reconcileChildren()由于传入的child为null,因此直接返回null
const domNode = document.getElementById('root');
const root = ReactDOM.createRoot(domNode);
root.render(
<div>
<span>
<p></p>
</span>
</div>
);如下面代码所示,此时next=null,触发completeUnitOfWork(),此时unitOfWork=<span>对应的fiber
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()方法创建原生DOMappendAllChildren():原生DOM之间的关联parentInstance.appendChild(child)workInProgress.stateNode = instance:将原生DOM赋值给fiber.stateNodefinalizeInitialChildren():初始化原生元素的一些事件处理和初始化属性,比如input输入框的input.value等等bubbleProperties()进行flags和lanes的冒泡:处理fiber.subtreeFlags和fiber.childLanes进行flags和lanes的冒泡合并
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,以及各种child和sibling的寻找,这其实涉及到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>
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相关的lanes和flags向上冒泡
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相关的lanes和flags向上冒泡
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属性的话,会删除它的所有子节点,并替换为一个具有给定值的文本节点(可以线上运行试试效果)
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的标记,不做任何处理
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的标记,不做任何处理
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的父级fiber(fiber.tag=HostComponent/HostRoot/HostPortal)getHostSibling():获取当前fiber的DOM兄弟节点insertOrAppendPlacementNodeIntoContainer():根据实际位置情况(可能有多层级嵌套)进行parentDom.appendChild(currentFiber.stateNode)添加到末尾- 或者
parentDom.insertBefore(currentFiber.stateNode,当前fiber的DOM兄弟节点)在某个位置前插入DOM
getHostSibling()和insertOrAppendPlacementNodeIntoContainer()的逻辑较为复杂,后续再做分析
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标记,因此不做任何处理
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:根类型,代表的是#rootHostComponent:原生标签类型,代表的是<div>、<span>HostText:文本类型,代表的是Child2
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()
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()
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
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
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,也有可能是字符串!
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,是一个纯文本,因此会触发
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()的逻辑也非常简单,就是创建一个文本元素
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相关的lanes和flags向上冒泡
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()方法创建原生DOMworkInProgress.stateNode = instance:将原生DOM赋值给fiber.stateNodefinalizeInitialChildren():初始化原生元素的一些事件处理和初始化属性,比如input输入框的input.value等等bubbleProperties()进行flags和lanes的冒泡:处理fiber.subtreeFlags和fiber.childLanes进行flags和lanes的冒泡合并
省略
Child2纯文本fiber对应的beginWork()和completeWork()分析,跟上面流程一致,beginWork()返回null,completeWork()创建文本元素
2.7 (div)HostComponent-completeWork
跟7.3 HostComponent-completeWork的流程一致:
createInstance():使用原生createElement()方法创建原生DOMappendAllChildren():原生DOM之间的关联parentInstance.appendChild(child),将所有child的dom都关联起来workInProgress.stateNode = instance:将原生DOM赋值给fiber.stateNodefinalizeInitialChildren():初始化原生元素的一些事件处理和初始化属性,比如input输入框的input.value等等bubbleProperties()进行flags和lanes的冒泡:处理fiber.subtreeFlags和fiber.childLanes进行flags和lanes的冒泡合并
2.8 HostRoot-completeWork
与
7.4 HostRoot-completeWork流程一致
由于我们初始化时hydrated=false,因此这里进行Snapshot标记
然后触发bubbleProperties(),将childrenFiber相关的lanes和flags向上冒泡
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举例分析
经过上面
HostRoot、HostComponent、HostText的举例分析,我们已经了解到render阶段中的beginWork()和completeWork()以及commit阶段中的commitBeforeMutaion、commitMutationEffects、commitLayoutEffects的具体执行逻辑,因此我们在这个Fragment中不会再进行具体的例子分析,而是直接根据这几个阶段去分析Fragment有何特殊的地方
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
一开始触发HostRoot的beginWork()->reconcileChildren()
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直接不渲染
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标记
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()进行children的fiber构建
注:
reconcileChildren()就是创建第二个<React.Fragment>的children对应的fiber,没什么特别的地方,这里就不展开分析了!
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()
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创建,因此目前root的childrenFiber如下图所示
<React.Fragment><p>
因此触发commit阶段时,我们可以直接忽视第一层<React.Fragment>,直接看第二层<React.Fragment>
在commitBeforeMutaion中,触发了HostRoot的clearContainerContent(),Fragment无任何处理
在commitMutationEffects中,触发了default分支,也是触发
recursivelyTraverseMutationEffects()commitReconciliationEffects()
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()操作,实现#root与childFiber.stateNode的关联!!
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关联的呢?
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()中,有几条规则:
- 我们会试着找当前
fiber的sibling,此时的<React.Fragment>的sibling=<p></p>,当然,如果node.sibling不存在,那么我们会试着往上再找一层,然后找它的sibling,因为可能存在下面的情况,当我们的fiber=<p>时,它的sibling为空,我们只能找它的return.sibling
如果当前
fiber的sibling为空,往上一层的fiber也为空 => 没必要往上找了,直接返回null如果当前
fiber的sibling为空,往上一层fiber又具备DOM,并不是下面这种<><p></p></>,那我们也不能拿上一层fiber的sibling作为参照物去插入DOM!=> 没必要往上找了,直接返回null
<div>
<React.Fragment>
<p></p>
</React.Fragment>
<span/>
</div>- 当我们找到了当前
fiber的sibling,我们还要判断它是不是具备DOM的fiber类型,比如Fragment的fiber类型是不具备DOM的,但是HostComponent的fiber类型是具备DOM的- 如果它不具备
DOM,我们要试一下它的child,比如上面示例中,我们的<React.Fragment>不具备DOM,但是它的<p>是有DOM的 - 如果它不具备
DOM,child又为空 => 继续找它的下一个sibling - 如果它不具备
DOM,本身又有Placement标记(本身就可能是插入或者移动),那我们就不能把它当作参照物,那还是得 => 继续找它的下一个sibling - 如果它具备
DOM,那它就很可能是我们需要的参照物,判断下是否有Placement标记(本身就可能是插入或者移动),有Placement标记,那还是得 => 继续找它的下一个sibling - 如果它具备
DOM,没有Placement标记,那它就是我们想要的稳定位置的参照物!!!
- 如果它不具备
在我们这个示例中:
- 找到了当前
fiber的sibling=<p></p> - 当前
fiber的sibling具备DOM,但是它有Placement标记(本身就可能是插入或者移动),那我们就不能把它当作参照物 => 继续找它的下一个sibling - 它的
sibling为空,往上一层fiber又具备DOM,我们不能拿上一层fiber的sibling作为参照物去插入DOM!=> 没必要往上找了,直接返回null
因此最终返回的_before为空,从而触发dom.appendChild()方法,而不是dom.insertBefore()方法
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举例分析
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
一开始触发HostRoot的beginWork()->reconcileChildren()
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为:
nextChildren = {
$$typeof: Symbol(react.element),
key: null,
props: { testProps: "app-children-wrapper" },
ref: null,
type: ƒ App(_ref),
_owner: null,
};最终触发的createChild()的createFiberFromElement()创建fiber
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
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 |= PerformedWorkworkInProgress.tag = FunctionComponent- 然后触发组件函数中子元素的渲染:
reconcileChildren(null, workInProgress, value)
注:传入的
current=null,触发ChildReconciler(false),不进行flags的标记!
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
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如下所示:
value = {
$$typeof: Symbol(react.element),
key: null,
props: { id: "parent", children: {...} },
ref: null,
type: "div",
_owner: null,
};继续触发
beginWork()跟上面HostComponent的beginWork()的流程一模一样,这里不再重复分析
4.2 completeWork
从上图的执行顺序,我们可以知道,函数组件中的HostComponent会先执行,然后逐渐向上执行
每一个HostComponent-completeWork()会执行createInstance() + appendAllChildren()不断创建DOM以及关联parentFiber.stateNode和fiber.stateNode的关系
直到IndeterminateComponent的completeWork()执行!其实也没执行什么,只是触发bubbleProperties()
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()
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()
与我们上面分析不同,这里的node是FunctionComponent,它是不具备DOM的!!!因此isHost=false,触发了第三个条件的代码
- 我们会直接取
node.child,也就是FunctionComponent中顶层元素<div>,然后触发insertOrAppendPlacementNodeIntoContainer(),这个时候node是HostComponent,具备DOM,因此可以执行插入操作,也就是#root.appendChild(<div/>) - 处理完
node.child还不够,我们还得处理下node.child.sibling,因此可能存在着FunctionComponent的顶层元素是一个<React.Fragment>的情况,它也是一个不具备DOM的类型,我们需要#root.appendChild(Fragment的childDOM)
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>的下一层级,那么就会在HostComponent的completeWork()触发appendAllChildren()逻辑
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的逻辑是一样的!!
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
一开始触发HostRoot的beginWork()->reconcileChildren()
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为:
nextChildren = {
$$typeof: Symbol(react.element),
key: null,
props: { },
ref: null,
type: ƒ App(_ref),
_owner: null,
};最终触发的createChild()的createFiberFromElement()创建fiber
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
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()方法
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():实例化ClassComponent,render()方法还没触发,只是刚刚初始化ClassComponentmountClassInstance():检测生命周期以及初始化一些变量,如果有componentDidMount,则打上Update和LayoutStatic标记(后续渲染更新再分析)finishClassComponent():ClassComponent.render()执行 +reconcileChildren()渲染子fiber
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!
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如下所示
instance = {
context: {},
props: {},
refs: {},
}而classComponentUpdater是一组工具对象,具备多个工具方法
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
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的渲染
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的创建和关联!
nextChildren = {
$$typeof: Symbol(react.element),
key: null,
props: { id: "我是App顶层元素div", children: {...} },
ref: null,
type: "div",
};5.2 completeWork()
没有执行什么方法,只是触发了bubbleProperties()
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的关联
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
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去处理以及渲染!!
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元素