请简述 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);
}
}
}
}

浙公网安备 33010602011771号