整体流程图(TODO)
1. HostRoot & HostComponent举例分析
当我们使用下面的测试代码时,涉及到有
HostRoot
:根类型,代表的是#root
HostComponent
:原生标签类型,代表的是<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
双缓冲树的根fiber
currentFirstChild
:旧的children
第一个元素,此时由于是首次渲染,因此为null
newChild
:新的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
=true
newFiber.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()
方法创建原生DOM
appendAllChildren()
:原生DOM
之间的关联parentInstance.appendChild(child)
workInProgress.stateNode = instance
:将原生DOM
赋值给fiber.stateNode
finalizeInitialChildren()
:初始化原生元素的一些事件处理和初始化属性,比如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
:根类型,代表的是#root
HostComponent
:原生标签类型,代表的是<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()
方法创建原生DOM
workInProgress.stateNode = instance
:将原生DOM
赋值给fiber.stateNode
finalizeInitialChildren()
:初始化原生元素的一些事件处理和初始化属性,比如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()
方法创建原生DOM
appendAllChildren()
:原生DOM
之间的关联parentInstance.appendChild(child)
,将所有child
的dom
都关联起来workInProgress.stateNode = instance
:将原生DOM
赋值给fiber.stateNode
finalizeInitialChildren()
:初始化原生元素的一些事件处理和初始化属性,比如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 |= PerformedWork
workInProgress.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()
方法还没触发,只是刚刚初始化ClassComponent
mountClassInstance()
:检测生命周期以及初始化一些变量,如果有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元素