请简述 React 16 版本中 commit 阶段的三个子阶段分别做了什么事情
1. before mutation阶段 (操作 Dom 前)
更新情况下:`主要调用类组件生命周期函数getSnapshotBeforeUpdate,并且把旧的props和旧的states传递进去
// commit 阶段的第一个子阶段
// 调用类组件的 getSnapshotBeforeUpdate 生命周期函数
function commitBeforeMutationEffects() {
// 循环 effect 链
while (nextEffect !== null) {
// nextEffect 是 effect 链上从 firstEffect 到 lastEffect
// 的每一个需要commit的 fiber 对象
// 初始化渲染第一个 nextEffect 为 App 组件
// effectTag => 3
const effectTag = nextEffect.effectTag;
// console.log(effectTag);
// nextEffect = null;
// return;
// 如果 fiber 对象中里有 Snapshot 这个 effectTag 的话
// Snapshot 和更新有关系 初始化渲染 不执行
if ((effectTag & Snapshot) !== NoEffect) {
// 开发环境执行 忽略
setCurrentDebugFiberInDEV(nextEffect);
// 计 effect 的数
recordEffect();
// 获取当前 fiber 节点
const current = nextEffect.alternate;
// 当 nextEffect 上有 Snapshot 这个 effectTag 时
// 执行以下方法, 主要是类组件调用 getSnapshotBeforeUpdate 生命周期函数
commitBeforeMutationEffectOnFiber(current, nextEffect);
// 开发环境执行 忽略
resetCurrentDebugFiberInDEV();
}
// 调度 useEffect
// 初始化渲染 目前没有 不执行
// false
if ((effectTag & Passive) !== NoEffect) {
// If there are passive effects, schedule a callback to flush at
// the earliest opportunity.
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalPriority, () => {
// 触发useEffect
flushPassiveEffects();
return null;
});
}
}
nextEffect = nextEffect.nextEffect;
}
}
class MyComponent extends React.Component {
divRef = React.createRef();
getSnapshotBeforeUpdate(prevProps, prevState) {
log.push('getSnapshotBeforeUpdate');
expect(this.divRef.current.textContent).toBe(
`value:${prevProps.value}`,
);
return 'foobar';
}
}
2. mutation阶段 (执行 Dom 操作)
`获取对象的 effects, 根据不同的 effectTag 执行不同的操作,插入,更新,删除。将 workInProgress Fiber 树变成 current Fiber 树
``插入节点:commitPlacement
``更新节点:commitWork
``删除节点:commitDeletion
// commit 阶段的第二个子阶段 // 根据 effectTag 执行 DOM 操作 function commitMutationEffects(root: FiberRoot, renderPriorityLevel) { // 循环 effect 链 while (nextEffect !== null) { // 开发环境执行 忽略 setCurrentDebugFiberInDEV(nextEffect); // 获取 effectTag // 初始渲染第一次循环为 App 组件 // 即将根组件及内部所有内容一次性添加到页面中 const effectTag = nextEffect.effectTag; // 如果有文本节点, 将 value 置为'' if (effectTag & ContentReset) { commitResetTextContent(nextEffect); } // 更新 ref if (effectTag & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } } // 根据 effectTag 分别处理 let primaryEffectTag = effectTag & (Placement | Update | Deletion | Hydrating); // 匹配 effectTag // 初始渲染 primaryEffectTag 为 2 匹配到 Placement switch (primaryEffectTag) { // 针对该节点及子节点进行插入操作 case Placement: { commitPlacement(nextEffect); // effectTag 从 3 变为 1 // 从 effect 标签中清除 "placement" 重置 effectTag 值 // 以便我们知道在调用诸如componentDidMount之类的任何生命周期之前已将其插入。 nextEffect.effectTag &= ~Placement; break; } // 插入并更新 DOM case PlacementAndUpdate: { // 插入 commitPlacement(nextEffect); // Clear the "placement" from effect tag so that we know that this is // inserted, before any life-cycles like componentDidMount gets called. nextEffect.effectTag &= ~Placement; // 更新 const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // 服务器端渲染 case Hydrating: { nextEffect.effectTag &= ~Hydrating; break; } // 服务器端渲染 case HydratingAndUpdate: { nextEffect.effectTag &= ~Hydrating; // Update const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // 更新 DOM case Update: { const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // 删除 DOM case Deletion: { commitDeletion(root, nextEffect, renderPriorityLevel); break; } } // TODO: Only record a mutation effect if primaryEffectTag is non-zero. recordEffect(); resetCurrentDebugFiberInDEV(); nextEffect = nextEffect.nextEffect; } } // 挂载 DOM 元素 function commitPlacement(finishedWork: Fiber): void { // finishedWork 初始化渲染时为根组件 Fiber 对象 if (!supportsMutation) { return; } // 获取非组件父级 Fiber 对象 // 初始渲染时为 <div id="root"></div> const parentFiber = getHostParentFiber(finishedWork); // 存储真正的父级 DOM 节点对象 let parent; // 是否为渲染容器 // 渲染容器和普通react元素的主要区别在于是否需要特殊处理注释节点 let isContainer; // 获取父级 DOM 节点对象 // 但是初始渲染时 rootFiber 对象中的 stateNode 存储的是 FiberRoot const parentStateNode = parentFiber.stateNode; // 判断父节点的类型 // 初始渲染时是 hostRoot 3 switch (parentFiber.tag) { case HostComponent: parent = parentStateNode; isContainer = false; break; case HostRoot: // 获取真正的 DOM 节点对象 // <div id="root"></div> parent = parentStateNode.containerInfo; // 是 container 容器 isContainer = true; break; case HostPortal: parent = parentStateNode.containerInfo; isContainer = true; break; case FundamentalComponent: if (enableFundamentalAPI) { parent = parentStateNode.instance; isContainer = false; } // eslint-disable-next-line-no-fallthrough 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) { // 在进行任何插入操作前, 需要先将 value 置为 '' resetTextContent(parent); // 清除 ContentReset 这个 effectTag parentFiber.effectTag &= ~ContentReset; } // 查看当前节点是否有下一个兄弟节点 // 有, 执行 insertBefore // 没有, 执行 appendChild const before = getHostSibling(finishedWork); // 渲染容器 if (isContainer) { // 向父节点中追加节点 或者 将子节点插入到 before 节点的前面 insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent); } else { // 非渲染容器 // 向父节点中追加节点 或者 将子节点插入到 before 节点的前面 insertOrAppendPlacementNode(finishedWork, before, parent); } } // 向容器中追加 | 插入到某一个节点的前面 function insertOrAppendPlacementNodeIntoContainer( node: Fiber, before: ?Instance, parent: Container, ): void { const {tag} = node; // 如果待插入的节点是一个 DOM 元素或者文本的话 // 比如 组件fiber => false div => true const isHost = tag === HostComponent || tag === HostText; if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) { // 获取 DOM 节点 const stateNode = isHost ? node.stateNode : node.stateNode.instance; // 如果 before 存在 if (before) { // 插入到 before 前面 insertInContainerBefore(parent, stateNode, before); } else { // 追加到父容器中 appendChildToContainer(parent, stateNode); } } else if (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 { // 如果是组件节点, 比如 ClassComponent, 则找它的第一个子节点(DOM 元素) // 进行插入操作 const child = node.child; if (child !== null) { // 向父级中追加子节点或者将子节点插入到 before 的前面 insertOrAppendPlacementNodeIntoContainer(child, before, parent); // 获取下一个兄弟节点 let sibling = child.sibling; // 如果兄弟节点存在 while (sibling !== null) { // 向父级中追加子节点或者将子节点插入到 before 的前面 insertOrAppendPlacementNodeIntoContainer(sibling, before, parent); // 同步兄弟节点 sibling = sibling.sibling; } } } } // 将 child 插入到父级中 export function appendChildToContainer( container: Container, child: Instance | TextInstance, ): void { let parentNode; // 监测 container 是否注释节点 if (container.nodeType === COMMENT_NODE) { // 获取父级的父级 parentNode = (container.parentNode: any); // 将子级节点插入到注释节点的前面 parentNode.insertBefore(child, container); } else { // 直接将 child 插入到父级中 parentNode = container; parentNode.appendChild(child); } // This container might be used for a portal. // If something inside a portal is clicked, that click should bubble // through the React tree. However, on Mobile Safari the click would // never bubble through the *DOM* tree unless an ancestor with onclick // event exists. So we wouldn't see it and dispatch it. // This is why we ensure that non React root containers have inline onclick // defined. // https://github.com/facebook/react/issues/11918 const reactRootContainer = container._reactRootContainer; if ( (reactRootContainer === null || reactRootContainer === undefined) && parentNode.onclick === null ) { // TODO: This cast may not be sound for SVG, MathML or custom elements. trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement)); } } root.current = finishedWork
3. Layout 阶段 (执行 Dom 操作后)
`调用类组件的生命周期
``初次渲染阶段调用componentDidMount生命周期函数
``更新阶段调用componentDidUpdate生命周期函数
``执行渲染完成之后的回调函数,也就是render函数的第三个参数,并且更改this指向,指向render方法的第一个参数
`调用函数组件的钩子函数
``firstEffect:指向第一个更新的节点
``nextEffect:指向下一个更新的节点
// commit 阶段的第三个子阶段
function commitLayoutEffects(
root: FiberRoot,
committedExpirationTime: ExpirationTime,
) {
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);
// 此时 effectTag 已经被重置为 1, 表示 DOM 操作已经完成
const effectTag = nextEffect.effectTag;
// 调用生命周期函数和钩子函数
// 前提是类组件中调用了生命周期函数
// 或者函数组件中调用了 useEffect
if (effectTag & (Update | Callback)) {
recordEffect();
const current = nextEffect.alternate;
// 类组件处理生命周期函数
// 函数组件处理钩子函数
commitLayoutEffectOnFiber(
root,
current,
nextEffect,
committedExpirationTime,
);
}
// 赋值ref
// false
if (effectTag & Ref) {
recordEffect();
commitAttachRef(nextEffect);
}
resetCurrentDebugFiberInDEV();
// 更新循环条件
nextEffect = nextEffect.nextEffect;
}
}
// 类组件处理生命周期函数
// 函数组件处理钩子函数
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');
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
// 调用 componentDidMount 生命周期函数
instance.componentDidMount();
stopPhaseTimer();
} else {
// 更新阶段
// 获取旧的 props
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(finishedWork.type, current.memoizedProps);
// 获取旧的 state
const prevState = current.memoizedState;
startPhaseTimer(finishedWork, 'componentDidUpdate');
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
// 调用 componentDidUpdate 生命周期函数
// instance.__reactInternalSnapshotBeforeUpdate 快照
// getSnapShotBeforeUpdate 方法的返回值
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
stopPhaseTimer();
}
}
// 获取任务队列
const updateQueue = finishedWork.updateQueue;
// 如果任务队列存在
if (updateQueue !== null) {
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
/**
* 调用 ReactElement 渲染完成之后的回调函数
* 即 render 方法的第三个参数
*/
commitUpdateQueue(finishedWork, updateQueue, instance);
}
return;
}
}
}
/**
* 执行渲染完成之后的回调函数
*/
export function commitUpdateQueue<State>(
finishedWork: Fiber,
finishedQueue: UpdateQueue<State>,
instance: any,
): void {
// effects 为数组, 存储任务对象 (Update 对象)
// 但前提是在调用 render 方法时传递了回调函数, 就是 render 方法的第三个参数
const effects = finishedQueue.effects;
// 重置 finishedQueue.effects 数组
finishedQueue.effects = null;
// 如果传递了 render 方法的第三个参数, effect 数组就不会为 null
if (effects !== null) {
// 遍历 effect 数组
for (let i = 0; i < effects.length; i++) {
// 获取数组中的第 i 个需要执行的 effect
const effect = effects[i];
// 获取 callback 回调函数
const callback = effect.callback;
// 如果回调函数不为 null
if (callback !== null) {
// 清空 effect 中的 callback
effect.callback = null;
// 执行回调函数
callCallback(callback, instance);
}
}
}
}