function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
// renderRoot 中也是 true, 表示正在进行更新操作
isWorking = true;
// 标记提交阶段
isCommitting = true;
startCommitTimer();
// 保存当前正在更新的 expirationTime
const committedExpirationTime = root.pendingCommitExpirationTime;
root.pendingCommitExpirationTime = NoWork;
// Update the pending priority levels to account for the work that we are
// about to commit. This needs to happen before calling the lifecycles, since
// they may schedule additional updates.
// 自己的 expirationTime,
const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
// 所有子树中优先级最高的任务
const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
// 找到这两个当中最小且非 NoWork 的 expirationTime
const earliestRemainingTimeBeforeCommit =
updateExpirationTimeBeforeCommit === NoWork ||
(childExpirationTimeBeforeCommit !== NoWork &&
childExpirationTimeBeforeCommit < updateExpirationTimeBeforeCommit)
? childExpirationTimeBeforeCommit
: updateExpirationTimeBeforeCommit;
// 标记优先级
markCommittedPriorityLevels(root, earliestRemainingTimeBeforeCommit);
// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;
let firstEffect;
//
if (finishedWork.effectTag > PerformedWork) {
// A fiber's effect list consists only of its children, not itself. So if
// the root has an effect, we need to add it to the end of the list. The
// resulting list is the set that would belong to the root's parent, if
// it had one; that is, all the effects in the tree including the root.
if (finishedWork.lastEffect !== null) {
// 自身的如果有 effect 且 lastEffect 不为 null,需要加入到单链表的最后
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else {
firstEffect = finishedWork;
}
} else {
// There is no effect on the root.
// 没有 effect 就用第一个effect
firstEffect = finishedWork.firstEffect;
}
// ...三次循环
prepareForCommit(root.containerInfo);
// Invoke instances of getSnapshotBeforeUpdate before mutation.
// 每次循环完都需要重新赋值全局变量 nextEffect = firstEffect ,即从头到尾开始走三次循环
nextEffect = firstEffect;
startCommitSnapshotEffectsTimer();
// 第一次循环, 调用 classComponent 可能存在的 getSnapshotBeforeUpdate 生命周期方法
while (nextEffect !== null) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
commitBeforeMutationLifecycles();
} catch (e) {
didError = true;
error = e;
}
}
if (didError) {
invariant(
nextEffect !== null,
'Should have next effect. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
captureCommitPhaseError(nextEffect, error);
// Clean-up
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}
stopCommitSnapshotEffectsTimer();
if (enableProfilerTimer) {
// Mark the current commit time to be shared by all Profilers in this batch.
// This enables them to be grouped later.
recordCommitTime();
}
// Commit all the side-effects within a tree. We'll do this in two passes.
// The first pass performs all the host insertions, updates, deletions and
// ref unmounts.
nextEffect = firstEffect;
startCommitHostEffectsTimer();
// 第二次循环,操作 dom 节点的插入,删除,更新操作
while (nextEffect !== null) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(null, commitAllHostEffects, null);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
// 操作 dom 节点的插入、删除、更新操作
commitAllHostEffects();
} catch (e) {
didError = true;
error = e;
}
}
if (didError) {
invariant(
nextEffect !== null,
'Should have next effect. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
captureCommitPhaseError(nextEffect, error);
// Clean-up
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}
stopCommitHostEffectsTimer();
resetAfterCommit(root.containerInfo);
// The work-in-progress tree is now the current tree. This must come after
// the first pass of the commit phase, so that the previous tree is still
// current during componentWillUnmount, but before the second pass, so that
// the finished work is current during componentDidMount/Update.
root.current = finishedWork;
// In the second pass we'll perform all life-cycles and ref callbacks.
// Life-cycles happen as a separate pass so that all placements, updates,
// and deletions in the entire tree have already been invoked.
// This pass also triggers any renderer-specific initial effects.
nextEffect = firstEffect;
startCommitLifeCyclesTimer();
// 第三次循环,组件的生命周期方法调用
while (nextEffect !== null) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(
null,
commitAllLifeCycles,
null,
root,
committedExpirationTime,
);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
// 组件生命周期调用
commitAllLifeCycles(root, committedExpirationTime);
} catch (e) {
didError = true;
error = e;
}
}
if (didError) {
invariant(
nextEffect !== null,
'Should have next effect. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
captureCommitPhaseError(nextEffect, error);
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}
// 全局遍量 reset
isCommitting = false;
isWorking = false;
stopCommitLifeCyclesTimer();
stopCommitTimer();
onCommitRoot(finishedWork.stateNode);
// 在执行 classComponent 的生命周期方法时又可能产生新的更新,childExpirationTime 可能会改变
const updateExpirationTimeAfterCommit = finishedWork.expirationTime;
const childExpirationTimeAfterCommit = finishedWork.childExpirationTime;
const earliestRemainingTimeAfterCommit =
updateExpirationTimeAfterCommit === NoWork ||
(childExpirationTimeAfterCommit !== NoWork &&
childExpirationTimeAfterCommit < updateExpirationTimeAfterCommit)
? childExpirationTimeAfterCommit
: updateExpirationTimeAfterCommit;
if (earliestRemainingTimeAfterCommit === NoWork) {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
legacyErrorBoundariesThatAlreadyFailed = null;
}
// 设置 root.expirationTime 为新计算出来的上面两个值中 小的并且非 NoWork 的 expirationTime
onCommit(root, earliestRemainingTimeAfterCommit);
}
// ReactFiberScheduler.js 中的 commitBeforeMutationLifecycles 方法
function commitBeforeMutationLifecycles() {
while (nextEffect !== null) {
if (__DEV__) {
ReactCurrentFiber.setCurrentFiber(nextEffect);
}
const effectTag = nextEffect.effectTag;
// 如果有 snapshot effect,在 beginWork 中更新 classComponent -> updateInstance 中如果组件实例有 getSnapshotBeforeUpdate 则会标记 snapshot
if (effectTag & Snapshot) {
recordEffect();
const current = nextEffect.alternate; // workInProgress
// 这里调用的是 ReactFiberCommitWork.js 中的同名方法
commitBeforeMutationLifeCycles(current, nextEffect);
}
// Don't cleanup effects yet;
// This will be done by commitAllLifeCycles()
nextEffect = nextEffect.nextEffect;
}
}
// 只有 classComponent 才会调用 getSnapshotBeforeUpdate
function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
case ClassComponent: {
if (finishedWork.effectTag & Snapshot) {
// 非首次渲染
if (current !== null) {
// 老 props
const prevProps = current.memoizedProps;
// 老 state
const prevState = current.memoizedState;
startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
// 组件实例
const instance = finishedWork.stateNode;
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
// 获取快照, getSnapshotBeforeUpdate 方法里可以调用 this
const snapshot = instance.getSnapshotBeforeUpdate(
prevProps,
prevState,
);
if (__DEV__) {}
// 获取的快照挂载在 instance 上,componentDidUpdate 方法的第三个参数即 snapshot
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
stopPhaseTimer();
}
}
return;
}
case HostRoot:
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
return;
default: {}
}
}
第二次循环 commitAllHostEffects
操作 dom 节点的插入、删除、更新操作
操作 ref
function commitAllHostEffects() {
while (nextEffect !== null) {
recordEffect();
const effectTag = nextEffect.effectTag;
// 判断内部是否为文字节点
if (effectTag & ContentReset) {
// 文字节点设置为空字符串
commitResetTextContent(nextEffect);
}
// ref操作
if (effectTag & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current);
}
}
// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every
// possible bitmap value, we remove the secondary effects from the
// effect tag and switch on that value.
// 插入、更新、删除的 effect
let primaryEffectTag = effectTag & (Placement | Update | Deletion);
switch (primaryEffectTag) {
// 新增的,插入
case Placement: {
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted
// does and isMounted is deprecated anyway so we should be able
// to kill this.
// 执行插入操作后,要去掉 effectTag
nextEffect.effectTag &= ~Placement;
break;
}
// 插入且更新
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
// 执行插入操作后,要去掉 effectTag
nextEffect.effectTag &= ~Placement;
// Update
// 节点之前存在,有新的 content 或者 attribute 要更新 ,执行 commitWork
// 只会更新 HostComponent, HostText
// nextEffect 为新的
// current 为老的
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// 更新
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion: {
commitDeletion(nextEffect);
break;
}
}
nextEffect = nextEffect.nextEffect;
}
function commitPlacement(finishedWork: Fiber): void {
// ReactDOM 调用 supportsMutation 默认为 true
if (!supportsMutation) {
return;
}
// Recursively insert all host nodes into the parent.
// 往上找到第一个节点类型是 原生dom节点、Root 节点、portal的父节点
const parentFiber = getHostParentFiber(finishedWork);
// Note: these two variables *must* always be updated together.
let parent;
// 是否为容器型
let isContainer;
switch (parentFiber.tag) {
case HostComponent:
parent = parentFiber.stateNode;
isContainer = false;
break;
case HostRoot:
parent = parentFiber.stateNode.containerInfo;
isContainer = true;
break;
case HostPortal:
parent = parentFiber.stateNode.containerInfo;
isContainer = true;
break;
default:
invariant(
false,
'Invalid host parent fiber. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
}
if (parentFiber.effectTag & ContentReset) {
// Reset the text content of the parent before doing any insertions
// 清除父节点的文本节点
resetTextContent(parent);
// Clear ContentReset from the effect tag
parentFiber.effectTag &= ~ContentReset;
}
// 找需要使用 insertBefore 来插入的节点 , parent.insertBefore(newNode, beforeNode)
const before = getHostSibling(finishedWork);
// We only have the top Fiber that was inserted but we need recurse down its
// children to find all the terminal nodes.
let node: Fiber = finishedWork;
while (true) {
// 原生 dom 节点 或者 文本节点
if (node.tag === HostComponent || node.tag === HostText) {
// insertBefore 插入
if (before) {
if (isContainer) {
insertInContainerBefore(parent, node.stateNode, before);
} else {
insertBefore(parent, node.stateNode, before);
}
} else {
// appendChild
if (isContainer) {
appendChildToContainer(parent, node.stateNode);
} else {
appendChild(parent, node.stateNode);
}
}
} else if (node.tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
// 这里和 completeWork 里的 appendAllChildren 类似
// 即节点不是 dom 节点(文本节点), 并且 node.child 不为 null,应该要遍历其子节点,其实就是 classComponent
node.child.return = node;
node = node.child;
continue;
}
if (node === finishedWork) {
return;
}
// 深度优先遍历
// 这个小循环找有兄弟节点的父节点,如果 sibling 不为 null 则会去遍历兄弟节点
// 如果没有兄弟节点,继续往上找,直到没有父节点 或者 父节点为当前遍历的节点时停止循环
while (node.sibling === null) {
if (node.return === null || node.return === finishedWork) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
// 继续遍历兄弟节点
node = node.sibling;
}
}
getHostParentFiber
往上找第一个节点类型是 原生 dom 节点、Root 节点、portal 的父节点
function getHostParentFiber(fiber: Fiber): Fiber {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
}
#### getHostSibling
- 找插入节点的参考节点: 即第一个类型是 `HostComponent` 或者 `HostText` 的兄弟节点
- 如果没有兄弟节点,则找往上找第一个是 `dom 节点` 的父节点,或者从父节点里的兄弟节点里找第一个 dom 节点
- 参考节点的 `effectTag` 不是 `placement` 才返回
```js
function getHostSibling(fiber: Fiber): ?Instance {
// We're going to search forward into the tree until we find a sibling host
// node. Unfortunately, if multiple insertions are done in a row we have to
// search past them. This leads to exponential search for the next sibling.
// TODO: Find a more efficient way to do this.
// fiber 是待插入(insertBefore 或者 appendChild )的那个节点,如果要使用 insertBefore 就要找到它的第一个原生 dom 节点的兄弟节点
let node: Fiber = fiber;
siblings: while (true) {
// If we didn't find anything, let's try the next sibling.
// 如果没有兄弟节点,则一直往上找父节点,直到找到父节点是 HostComponent、HostRoot、HostPortal这三种或者为null为止,即确定父节点肯定是原生 dom 节点
// 或者从父节点的兄弟节点里找 dom 节点
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// If we pop out of the root or hit the parent the fiber we are the
// last sibling.
return null;
}
node = node.return;
}
// 有兄弟节点的情况
node.sibling.return = node.return;
// 从兄弟节点开始找第一个原生 dom 节点
node = node.sibling;
// 如果不是原生 dom 节点 或者 text,那么就要去找它(classComponent)的 child
while (node.tag !== HostComponent && node.tag !== HostText) {
// If it is not host node and, we might have a host node inside it.
// Try to search down until we find one.
// 如果这个节点也是标记插入,那么要跳过
if (node.effectTag & Placement) {
// If we don't have a child, try the siblings instead.
continue siblings;
}
// If we don't have a child, try the siblings instead.
// We also skip portals because they are not part of this host tree.
// 没有 child 或者是 portal 跳过
if (node.child === null || node.tag === HostPortal) {
continue siblings;
} else {
// 从 child 里找第一个 dom 节点
node.child.return = node;
node = node.child;
}
}
// Check if this host node is stable or about to be placed.
// 到这 node 肯定是原生 dom 节点了,并且是要没有标记插入的才算真正找到
if (!(node.effectTag & Placement)) {
// Found it!
return node.stateNode;
}
}
}
// current 是老的节点
// newEffect 是新的节点
const current = nextEffect.alternate;
commitWork(current, nextEffect);
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
if (!supportsMutation) {
commitContainer(finishedWork);
return;
}
switch (finishedWork.tag) {
case ClassComponent: {
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// 节点已经创建
if (instance != null) {
// Commit the work prepared earlier.
// 新节点上的 props
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
// 老节点上的 props
const oldProps = current !== null ? current.memoizedProps : newProps;
// dom 标签
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
// updatePayload 为 diffProperties 是创建的 [k1, v1, k2, v2] 格式的需要更新的 prop 的数组
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
const textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string =
current !== null ? current.memoizedProps : newText;
// 文本节点直接设置 textInstance.nodeValue = newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
return;
}
case Profiler: {
return;
}
case SuspenseComponent: {
return;
}
case IncompleteClassComponent: {
return;
}
default: {
}
}
}
commitUpdate
更新 dom 上挂载的 props
更新 property
function commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps);
}
updateProperties
将 updatePayload 上需要更新的属性更新
对 input radio 标签单独处理,更新 checked
function updateProperties(
domElement: Element,
updatePayload: Array<any>,
tag: string,
lastRawProps: Object,
nextRawProps: Object,
): void {
// Update checked *before* name.
// In the middle of an update, it is possible to have multiple checked.
// When a checked radio tries to change name, browser makes another radio's checked false.
if (
tag === 'input' &&
nextRawProps.type === 'radio' &&
nextRawProps.name != null
) {
ReactDOMInput.updateChecked(domElement, nextRawProps);
}
const wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
const isCustomComponentTag = isCustomComponent(tag, nextRawProps);
// Apply the diff.
// 更新 property
updateDOMProperties(
domElement,
updatePayload,
wasCustomComponentTag,
isCustomComponentTag,
);
// TODO: Ensure that an update gets scheduled if any of the special props
// changed.
// 对可交互的标签单独处理
switch (tag) {
case 'input':
// Update the wrapper around inputs *after* updating props. This has to
// happen after `updateDOMProperties`. Otherwise HTML5 input validations
// raise warnings and prevent the new value from being assigned.
ReactDOMInput.updateWrapper(domElement, nextRawProps);
break;
case 'textarea':
ReactDOMTextarea.updateWrapper(domElement, nextRawProps);
break;
case 'select':
// <select> value update needs to occur after <option> children
// reconciliation
ReactDOMSelect.postUpdateWrapper(domElement, nextRawProps);
break;
}
}
updateDOMProperties
将 updatePayload [k1, v1, k2, v2] 遍历更新
// 和 completeWork 中 finalizeInitialChildren 初始化属性做的事相似
function updateDOMProperties(
domElement: Element,
updatePayload: Array<any>,
wasCustomComponentTag: boolean,
isCustomComponentTag: boolean,
): void {
// TODO: Handle wasCustomComponentTag
for (let i = 0; i < updatePayload.length; i += 2) {
const propKey = updatePayload[i];
const propValue = updatePayload[i + 1];
if (propKey === STYLE) {
// 设置 style 属性,
CSSPropertyOperations.setValueForStyles(domElement, propValue);
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
// 设置 html
setInnerHTML(domElement, propValue);
} else if (propKey === CHILDREN) {
// 设置 textContent
setTextContent(domElement, propValue);
} else {
// 设置 dom 属性
DOMPropertyOperations.setValueForProperty(
domElement,
propKey,
propValue,
isCustomComponentTag,
);
}
}
}
Deletion 删除:commitDeletion
遍历子树
卸载 ref
classComponent 执行 componentWillUnmount 方法
清空 fiber 上的相关属性
function commitDeletion(current: Fiber): void {
if (supportsMutation) {
// Recursively delete all host nodes from the parent.
// Detach refs and call componentWillUnmount() on the whole subtree.
// ReactDOM 只会走这个方法
unmountHostComponents(current);
} else {
// Detach refs and call componentWillUnmount() on the whole subtree.
commitNestedUnmounts(current);
}
// 清空 Fiber 上的属性
detachFiber(current);
}
unmountHostComponents
function unmountHostComponents(current): void {
// We only have the top Fiber that was deleted but we need recurse down its
// children to find all the terminal nodes.
let node: Fiber = current;
// Each iteration, currentParent is populated with node's host parent if not
// currentParentIsValid.
let currentParentIsValid = false;
// Note: these two variables *must* always be updated together.
let currentParent;
let currentParentIsContainer;
while (true) {
if (!currentParentIsValid) {
// 找到 parent 是 HostComponent、HostRoot、HostPortal 类型的节点赋值给 currentParent
let parent = node.return;
findParent: while (true) {
switch (parent.tag) {
case HostComponent:
currentParent = parent.stateNode;
currentParentIsContainer = false;
break findParent;
case HostRoot:
currentParent = parent.stateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
case HostPortal:
currentParent = parent.stateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
}
parent = parent.return;
}
currentParentIsValid = true;
}
if (node.tag === HostComponent || node.tag === HostText) {
// 原生 dom 节点的子节点下 也有 classComponent 或者 portal,因此要递归调用遍历整个树的每个节点
commitNestedUnmounts(node);
// After all the children have unmounted, it is now safe to remove the
// node from the tree.
// 如果是原生 dom 节点,直接 removeChild 删除
if (currentParentIsContainer) {
removeChildFromContainer((currentParent: any), node.stateNode);
} else {
removeChild((currentParent: any), node.stateNode);
}
// Don't visit children because we already visited them.
} else if (node.tag === HostPortal) {
// When we go into a portal, it becomes the parent to remove from.
// We will reassign it back when we pop the portal on the way up.
// 如果是 HostPortal 继续循环子节点
currentParent = node.stateNode.containerInfo;
currentParentIsContainer = true;
// Visit children because portals might contain host components.
if (node.child !== null) {
// 子节点不为 null 继续往下遍历
node.child.return = node;
node = node.child;
continue;
}
} else {
// 非 dom 节点 和非 portal ,即 classComponent
commitUnmount(node);
// Visit children because we may find more host components below.
if (node.child !== null) {
// 子节点不为 null 继续往下遍历
node.child.return = node;
node = node.child;
continue;
}
}
if (node === current) {
return;
}
// 深度遍历,往上找 sibling
while (node.sibling === null) {
if (node.return === null || node.return === current) {
return;
}
node = node.return;
if (node.tag === HostPortal) {
// When we go out of the portal, we need to restore the parent.
// Since we don't keep a stack of them, we will search for it.
currentParentIsValid = false;
}
}
node.sibling.return = node.return;
node = node.sibling;
}
}
commitUnmount
卸载 ref
执行 componentWillUnmount
对于 portal 会递归调用 unmountHostComponents
function commitUnmount(current: Fiber): void {
onCommitUnmount(current);
switch (current.tag) {
case ClassComponent: {
// 卸载 ref
safelyDetachRef(current);
const instance = current.stateNode;
// 执行 componentWillUnmount
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(current, instance);
}
return;
}
case HostComponent: {
// 卸载 ref
safelyDetachRef(current);
return;
}
case HostPortal: {
// TODO: this is recursive.
// We are also not using this parent because
// the portal will get pushed immediately.
if (supportsMutation) {
// 递归调用 unmountHostComponents
unmountHostComponents(current);
} else if (supportsPersistence) {
emptyPortalContainer(current);
}
return;
}
}
}
#### commitNestedUnmounts
- `HostComponent` 执行卸载,原生 `dom 节点` 的子节点下也有 `classComponent` 或者 `portal`
- 嵌套递归卸载子树达到卸载 `HostPortal` 的目的
```js
function commitNestedUnmounts(root: Fiber): void {
// While we're inside a removed host node we don't want to call
// removeChild on the inner nodes because they're removed by the top
// call anyway. We also want to call componentWillUnmount on all
// composites before this host node is removed from the tree. Therefore
// we do an inner loop while we're still inside the host node.
let node: Fiber = root;
while (true) {
// 如果 node 是 portal (只会发生在 portal 为 原生 dom 节点的 sibling 的情况,如果要卸载的组件的第一个 child 就是 portal,那么就会在 unmountHostComponents 里循环卸载子节点了),-> commitUnmount -> unmountHostComponents -> portal 会去遍历 child -> child 为 hostComponent -> commitNestedUnmounts -> commitUnmount 卸载 child
// commitUnmount 卸载 child -> portal 会执行 removeChildFromContainer -> 最后依次返回
// 嵌套递归调用卸载节点,达到卸载 HostPortal 的目的
commitUnmount(node);
// Visit children because they may contain more composite or host nodes.
// Skip portals because commitUnmount() currently visits them recursively.
if (
node.child !== null &&
// If we use mutation we drill down into portals using commitUnmount above.
// If we don't use mutation we drill down into portals here instead.
(!supportsMutation || node.tag !== HostPortal)
) {
// 不是 portal 并且有 child,继续循环
node.child.return = node;
node = node.child;
continue;
}
if (node === root) {
return;
}
// 深度遍历,往上找 sibling
while (node.sibling === null) {
if (node.return === null || node.return === root) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function commitAllLifeCycles(
finishedRoot: FiberRoot,
committedExpirationTime: ExpirationTime,
) {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
recordEffect();
// current 为老的
const current = nextEffect.alternate;
commitLifeCycles(
finishedRoot,
current,
nextEffect,
committedExpirationTime,
);
}
if (effectTag & Ref) {
recordEffect();
commitAttachRef(nextEffect);
}
const next = nextEffect.nextEffect;
// Ensure that we clean these up so that we don't accidentally keep them.
// I'm not actually sure this matters because we can't reset firstEffect
// and lastEffect since they're on every node, not just the effectful
// ones. So we have to clean everything as we reuse nodes anyway.
nextEffect.nextEffect = null;
// Ensure that we reset the effectTag here so that we can rely on effect
// tags to reason about the current life-cycle.
nextEffect = next;
}
}
commitLifeCycles
function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedExpirationTime: ExpirationTime,
): void {
switch (finishedWork.tag) {
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
// 首次渲染
if (current === null) {
startPhaseTimer(finishedWork, 'componentDidMount');
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidMount();
stopPhaseTimer();
} else {
// 更新渲染
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
startPhaseTimer(finishedWork, 'componentDidUpdate');
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidUpdate(
prevProps, // setState 后产生的新的 props
prevState, // setState 后产生的新的 state
instance.__reactInternalSnapshotBeforeUpdate, // Snapshot 快照
);
stopPhaseTimer();
}
}
const updateQueue = finishedWork.updateQueue;
// updateQueue 不为空,要执行 queue 里可能存在的 callback
if (updateQueue !== null) {
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
commitUpdateQueue(
finishedWork,
updateQueue,
instance,
committedExpirationTime,
);
}
return;
}
case HostRoot: {
// Root 节点, ReactDOM.render 方法会创建一个update,这个方法可以接受的第三个参数就是 callback,在第一次渲染结束后调用
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
commitUpdateQueue(
finishedWork,
updateQueue,
instance,
committedExpirationTime,
);
}
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
// 首次渲染且 effecttag 为 update,则会自动 focus 需要 autofocus 的节点
if (current === null && finishedWork.effectTag & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
// Button, Input, Select, Textarea 标签,props.autofocus 为真,则首次渲染会 focus
commitMount(instance, type, props, finishedWork);
}
return;
}
case HostText: {
// We have no life-cycles associated with text.
return;
}
case HostPortal: {
// We have no life-cycles associated with portals.
return;
}
case Profiler: {
}
case SuspenseComponent: {
if (finishedWork.effectTag & Callback) {
// In non-strict mode, a suspense boundary times out by commiting
// twice: first, by committing the children in an inconsistent state,
// then hiding them and showing the fallback children in a subsequent
// commit.
const newState: SuspenseState = {
alreadyCaptured: true,
didTimeout: false,
timedOutAt: NoWork,
};
finishedWork.memoizedState = newState;
scheduleWork(finishedWork, Sync);
return;
}
let oldState: SuspenseState | null =
current !== null ? current.memoizedState : null;
let newState: SuspenseState | null = finishedWork.memoizedState;
let oldDidTimeout = oldState !== null ? oldState.didTimeout : false;
let newDidTimeout;
let primaryChildParent = finishedWork;
if (newState === null) {
newDidTimeout = false;
} else {
newDidTimeout = newState.didTimeout;
if (newDidTimeout) {
primaryChildParent = finishedWork.child;
newState.alreadyCaptured = false;
if (newState.timedOutAt === NoWork) {
// If the children had not already timed out, record the time.
// This is used to compute the elapsed time during subsequent
// attempts to render the children.
newState.timedOutAt = requestCurrentTime();
}
}
}
if (newDidTimeout !== oldDidTimeout && primaryChildParent !== null) {
hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
}
return;
}
case IncompleteClassComponent:
break;
default: {}
}
}
commitUpdateQueue
如果还有有捕获错误的更新,需要清空
commitUpdateEffects 调用 setStatte 的回调函数
function commitUpdateQueue<State>(
finishedWork: Fiber,
finishedQueue: UpdateQueue<State>,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
// If the finished render included captured updates, and there are still
// lower priority updates left over, we need to keep the captured updates
// in the queue so that they are rebased and not dropped once we process the
// queue again at the lower priority.
// 如果有捕获错误的更新,即一次渲染过程中还没有完成的更新
if (finishedQueue.firstCapturedUpdate !== null) {
// Join the captured update list to the end of the normal list.
if (finishedQueue.lastUpdate !== null) {
// 如果还有普通的更新未完成(即低优先级的任务),将错误的更新加入到链表之后,将错误捕获的更新放到低优先级的任务上渲染
finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;
finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;
}
// Clear the list of captured updates.
// 如果没有低优先级任务,则清空本次渲染过程产生的捕获错误更新
finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;
}
// Commit the effects
// 调用 effect.callback, 一般是 setState 的回调函数
commitUpdateEffects(finishedQueue.firstEffect, instance);
finishedQueue.firstEffect = finishedQueue.lastEffect = null;
commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);
finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
}
function commitUpdateEffects<State>(
effect: Update<State> | null,
instance: any,
): void {
while (effect !== null) {
const callback = effect.callback;
if (callback !== null) {
effect.callback = null;
// 执行 callback
callCallback(callback, instance);
}
effect = effect.nextEffect;
}
}
commitRoot
renderRoot
最后会调用onComplete
方法设置root.finishedWork
(即RootFiber
) 和root.pendingCommitExpirationTime
performWorkOnRoot
如果finishedWork
不为null
,则调用completeRoot -> commitRoot
commit
阶段不能被中断,尽量少的任务交给commit
阶段三次循环,都是对
firstEffect
到lastEffect
单向链上根据不同effectTag
进行不同的更新第一次循环 commitBeforeMutationLifecycles
classComponent
可能存在的getSnapshotBeforeUpdate
生命周期方法classComponent
才会调用getSnapshotBeforeUpdate
将生成的快照挂载在
instance.__reactInternalSnapshotBeforeUpdate
上,在componentDidUpdate
里会接受这个参数第二次循环 commitAllHostEffects
dom
节点的插入、删除、更新操作操作
ref
}
commitDetachRef 清空ref
Placement 插入:commitPlacement
insertBefore
方法需要找父节点,getHostParentFiber
即往上找到第一个节点类型是原生 dom 节点
、Root 节点
、portal
的父节点getHostSibling
方法用来找插入的节点的参考节点parent.insertBefore(newNode, beforeNode)
, 如果有beforeNode
, 则调用insert
插入,否则是appendChild
getHostParentFiber
原生 dom 节点
、Root 节点
、portal
的父节点function isHostParent(fiber: Fiber): boolean { return ( fiber.tag === HostComponent || fiber.tag === HostRoot || fiber.tag === HostPortal ); }
insertBefore or appendChild
PlacementAndUpdate 插入和更新:commitPlacement、commitWork
Updates 更新:commitWork
commitPlacement
后要去掉placement
的effectTag
content
或者attribute
要更新时,执行commitWork
HostComponent,HostText
commitUpdate
dom
上挂载的props
property
updateProperties
updatePayload
上需要更新的属性更新对
input radio
标签单独处理,更新checked
updateDOMProperties
updatePayload [k1, v1, k2, v2]
遍历更新Deletion 删除:commitDeletion
ref
classComponent
执行componentWillUnmount
方法fiber
上的相关属性unmountHostComponents
commitUnmount
ref
componentWillUnmount
对于
portal
会递归调用unmountHostComponents
// safelyDetachRef function safelyDetachRef(current: Fiber) { const ref = current.ref; if (ref !== null) { if (typeof ref === 'function') { if (DEV) } else { try { ref(null); } catch (refError) { captureCommitPhaseError(current, refError); } } } else { ref.current = null; } } }
removeChildFromContainer & removeChild
第三次循环 commitAllLifeCycles
componentDidMount
, 更新渲染执行componentDidUpdate
setState
的callback
回调函数, (componentDidCatch
也是在callback
中执行)commitAllLifeCycles
effectTag
为Update
或者Callback
则去调用commitLifeCycles
commitLifeCycles
commitUpdateQueue
commitUpdateEffects
调用setStatte
的回调函数