| title | fiber树渲染 |
|---|
在 fiber 树构建中已经分析, 无论是否是首次 render, 都会在内存中构造最新的 fiber 树.
在 fiber 树提交渲染之前, 也就是commitRoot执行之前. 内存中有两棵 fiber 树, current和workInProgress指针分别指向这两棵树.
current- 代表当前正在使用的 fiber 树, 此 fiber 树上所有节点与当前页面上的 dom 节点相互引用
- 如果是首次 render,
current=null, 此刻页面还未渲染
workInProgress- 代表即将更新到页面的 fiber 树, 此刻该 fiber 树已经构造完成, 等待最后的渲染
在 beginWork 和 completeWork 阶段, 所有有副作用的 fiber 节点都被打上了标记. 并且全部收集到了HostRootFiber节点的副作用队列里面.
在后续同步更新到 DOM 的实现中, 副作用队列起到关键作用.
分析渲染实现类commitRootImpl的源码:
// ... 函数中省略了与首次render无关代码, 先关心主流程
function commitRootImpl(root, renderPriorityLevel) {
// 设置局部变量
const finishedWork = root.finishedWork;
const expirationTime = root.finishedExpirationTime;
// 更新FiberRoot对象上的属性
root.finishedWork = null;
root.finishedExpirationTime = NoWork;
root.callbackNode = null;
root.callbackExpirationTime = NoWork;
root.callbackPriority = NoPriority;
// Get the list of effects.
let firstEffect = finishedWork.firstEffect;
if (firstEffect !== null) {
// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;
// commit阶段分为3个阶段
// 1. before mutaion. 调用getSnapshotBeforeUpdate
nextEffect = firstEffect;
do {
commitBeforeMutationEffects();
} while (nextEffect !== null);
// 2. mutation. 调用渲染器, 更新到最新的Fiber状态
nextEffect = firstEffect;
do {
commitMutationEffects(root, renderPriorityLevel);
} while (nextEffect !== null
);
// FiberRoot的current指向finishedWork
// 在layout阶段之前, current指向的是HostRootFiber. 影响的生命周期函数: componentWillUnmount
// 在layout阶段, current指向的是RootFiber.alternate. 影响的生命周期函数: componentDidMount/Update
root.current = finishedWork;
// 3. layout
nextEffect = firstEffect;
do {
commitLayoutEffects(root, expirationTime);
} while (nextEffect !== null);
nextEffect = null;
executionContext = prevExecutionContext;
}
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
if (rootDoesHavePassiveEffects) {
// ...
} else {
// 设置effect链表中各个对象的nextEffect指针为null. 辅助垃圾回收
nextEffect = firstEffect;
while (nextEffect !== null) {
const nextNextEffect = nextEffect.nextEffect;
nextEffect.nextEffect = null;
nextEffect = nextNextEffect;
}
}
// Always call this before exiting `commitRoot`, to ensure that any
// additional work on this root is scheduled.
ensureRootIsScheduled(root);// 确保任何额外的改动都会被安排调度
// If layout work was scheduled, flush it now.
flushSyncCallbackQueue();// 如果layout阶段有调度更新, 在这里进行刷新可以知道 commit 分为 3 个阶段:
- dom 变化之前,
commitBeforeMutationEffects
- 调用
getSnapshotBeforeUpdate(非初次 render)
- 同步 fiber 更新到 dom 上,
commitMutationEffects
- 调用
componentWillUnmount(非初次 render) - 调用渲染器, 把
stateNode更新到FiberRoot.containerInfo节点之上. 此时浏览器会渲染出页面 - 本阶段执行完成之后,
FiberRoot.current = HostRootFiber.alternate,FiberRoot.current指针指向了HostRootFiber.alternate
- dom 变化之后,
commitLayoutEffects
- 调用
componentDidMount/Update
主要流程如下图表示:
注意当 3 个阶段都执行完成之后, 为了辅助垃圾回收, 会去除effects队列中所有effect的nextEffect指针
执行commitBeforeMutationEffects
// ... 函数中省略了与首次render无关代码, 先关心主流程
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if ((effectTag & Snapshot) !== NoEffect) {
const current = nextEffect.alternate;
commitBeforeMutationEffectOnFiber(current, nextEffect); // 如果current.effectTag中有Snapshot标记, 则执行生命周期函数getSnapshotBeforeUpdate
}
nextEffect = nextEffect.nextEffect;
}
}执行commitMutationEffects
// ... 函数中省略了与首次render无关代码, 先关心主流程
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
// TODO: Should probably move the bulk of this function to commitWork.
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
// 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.
const primaryEffectTag =
effectTag & (Placement | Update | Deletion | Hydrating);
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.
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.
nextEffect.effectTag &= ~Placement; //commitPlacement执行完成之后, 去除了Placement. 在componentDidMount之前可以判断该节点是否已经插入
// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// ...
}
nextEffect = nextEffect.nextEffect;
}
}// ... 函数中省略了与首次render无关代码, 先关心主流程
function commitPlacement(finishedWork: Fiber): void {
const parentFiber = getHostParentFiber(finishedWork);
// Note: these two variables *must* always be updated together.
let parent;
let isContainer;
const parentStateNode = parentFiber.stateNode;
switch (parentFiber.tag) {
case HostRoot:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
// ...
}
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
}// ... 函数中省略了无关代码, 先关心主流程
function insertOrAppendPlacementNodeIntoContainer(
node: Fiber,
before: ?Instance,
parent: Container,
): void {
// ...
appendChildToContainer(parent, stateNode);
}export function appendChildToContainer(
container: Container,
child: Instance | TextInstance,
): void {
let parentNode;
if (container.nodeType === COMMENT_NODE) {
parentNode = (container.parentNode: any);
parentNode.insertBefore(child, container);
} else {
parentNode = container;
parentNode.appendChild(child);
}
}执行commitLayoutEffects
// ... 函数中省略了无关代码, 先关心主流程
function commitLayoutEffects(
root: FiberRoot,
committedExpirationTime: ExpirationTime,
) {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
const current = nextEffect.alternate;
commitLayoutEffectOnFiber(
root,
current,
nextEffect,
committedExpirationTime,
);
}
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) {
instance.componentDidMount();
} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(finishedWork.type, current.memoizedProps);
const prevState = current.memoizedState;
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
}
}
}
}在commitRoot的最后会执行ensureRootIsScheduled确保FiberRoot已经被调度, 由于没有新的任务, 所以会退出.执行完commitRoot之后, 首次 render 过程就已经全部完成了.
当前工作空间的主要变量的状态如下:
初次 render 结束之后, 除了HostRootFiber节点有alternate属性之外, 其余的Fiber节点均无alternate.
最终完成ReactDOM.render函数.
ReactDOM.render(<App />, document.getElementById('root')); // 执行结束
