本文要弄清楚的问题
- commitPlacement 只处理两种类型:HostComponent 和 HostRoot,HostRoot我可以理解,最终要commitRoot嘛,但是HostComponent是什么情况?不是在completeWork就已经完成DOM的操作了吗?
前言
本文基于 HostRoot
、 HostText
、 HostComponent
、 FunctionComponent
进行非并发模式下的渲染更新流程分析,主要侧重于
- render阶段的 beginWork():进行diff比较,形成新的 fiber 数据
- render阶段的 completeWork():进行非root的DOM操作,比如判断是否有DOM,没有则创建,有的检测属性是否变化,打上 Update 的 flags 标记
- commit阶段的 commitMutationEffectsOnFiber():根据 flags/subtreeFlags 进行不同 flags 的处理,比如 Update进行数据更新、Placement进行DOM的插入(主要是
#root.appendChild(DOM元素)
)
本文不进行 ClassComponent 的分析
整体流程图(TODO)
整体渲染流程分析
根据整体流程图进行更新渲染流程的分析
HostRoot
beginWork()
与首次渲染一致,没什么特殊处理
completeWork()
与首次渲染一致,没什么特殊处理
commitMutationEffectsOnFiber()
与首次渲染一致,没什么特殊处理
HostText
beginWork()
与首次渲染一致,没什么特殊处理
completeWork()
比对 oldText 和 newText 是否不同,如果不同则打上 Update 的 flags 标记
case HostText: {
var newText = newProps;
if (current && workInProgress.stateNode != null) {
var oldText = current.memoizedProps;
updateHostText$1(current, workInProgress, oldText, newText);
} else {
//...
}
bubbleProperties(workInProgress);
return null;
}
updateHostText$1 = function (current, workInProgress, oldText, newText) {
if (oldText !== newText) {
markUpdate(workInProgress);
}
};
commitMutationEffectsOnFiber()
在 commit 阶段,如果有 Update 的标记,则进行文本的更新操作
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
var textInstance = finishedWork.stateNode;
var newText = finishedWork.memoizedProps;
var oldText = current !== null ? current.memoizedProps : newText;
// commitTextUpdate:textInstance.nodeValue = newText;
commitTextUpdate(textInstance, oldText, newText);
}
return;
}
HostComponent
beginWork()
与首次渲染一致,没什么特殊处理
completeWork()
当fiber可以被复用时,它的stateNode也可以被复用,在completeWork中,通过判断
workInProgress.stateNode !== null
阻止重新创建DOM?直接更新Component??
当更新执行到 HostComponent
时,会直接触发 updateComponent
的处理方法
function completeWork() {
// 存储了children数据!!可以看文章reconcileSingleElement()的分析
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode !== null) {
// 更新逻辑
updateHostComponent(current, workInProgress, type, newProps);
} else {
const instance = createInstance(type); // 创建dom
appendAllChildren(instance, workInProgress); //
workInProgress.stateNode = instance;
finalizeInitialChildren(instance, type);
}
bubbleProperties(workInProgress);
return null;
}
}
}
而 updateComponent
的逻辑也很简单,就是进行所有属性的比对,然后赋值到 updateQueue
,进行 Update
的标记!
updateHostComponent$1 = function () {
var oldProps = current.memoizedProps;
if (oldProps === newProps) {
return;
}
var instance = workInProgress.stateNode;
var updatePayload = prepareUpdate(instance, type, oldProps, newProps);
// 最终得到 updatePayload = ["children", "45"]
// updatePayload = 属性 + 新的值
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
};
function prepareUpdate() {
// diffProperties: 复杂的属性比对,根据style...等各种情况比对是否已经改变
return diffProperties(domElement, type, oldProps, newProps);
}
function markUpdate(workInProgress) {
workInProgress.flags |= Update;
}
commitMutationEffectsOnFiber()
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
var _instance4 = finishedWork.stateNode;
if (_instance4 != null) {
var newProps = finishedWork.memoizedProps;
var oldProps = current !== null ? current.memoizedProps : newProps;
var type = finishedWork.type;
var updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(_instance4, updatePayload, type, oldProps, newProps, finishedWork);
}
}
}
return;
}
}
function commitUpdate() {
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps); // Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
}
FunctionComponent
beginWork()
先进行 oldProps 和 newProps 的比对,如果有变化则设置 didReceiveUpdate
= true
,如果没有变化,则设置为 didReceiveUpdate
= false
然后触发 updateFunctionComponent()
function beginWork() {
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged()) {
didReceiveUpdate = true;
} else {
//...
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
//...
}
switch (workInProgress.tag) {
case FunctionComponent: {
//...
return updateFunctionComponent();
}
}
}
在 updateFunctionComponent()
中,渲染更新流程如果 didReceiveUpdate
= false
,说明没有变化,则不继续执行 diff 和生成 childrenFibers 的流程,也就是不需要该 fiber 进行渲染更新!
function updateFunctionComponent() {
prepareToReadContext(workInProgress, renderLanes);
nextChildren = renderWithHooks();
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function bailoutHooks(current, workInProgress, lanes) {
workInProgress.updateQueue = current.updateQueue;
workInProgress.flags &= ~(Passive | Update);
current.lanes = removeLanes(current.lanes, lanes);
}
completeWork()
与首次渲染一致,没什么特殊处理
commitMutationEffectsOnFiber()
根据 Update 的 flags 标记,触发
- 清理旧的useEffect
- 挂载新的useEffect(异步执行)
- 清理旧的useLayoutEffect(同步执行)
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
commitHookEffectListUnmount(
Insertion | HasEffect,
finishedWork,
finishedWork.return,
);
commitHookEffectListMount(Insertion | HasEffect, finishedWork);
commitHookEffectListUnmount(
Layout | HasEffect,
finishedWork,
finishedWork.return,
);
}
return;
}
那 FunctionComponent 的 Update 标记是在什么地方打上的呢?
总结
切换双缓冲树流程
在 createContainer()
中
- 创建
root
=new FiberRootNode()
对象(一个非常普通的对象具有一些特定的属性) - 创建
uninitializedFiber
=new FiberNode()
对象(一个非常普通的对象具有一些特定的属性) FiberRootNode
和FiberNode
相互绑定- 初始化
root
对应fiber
的state
对象,放入fiber.memoizedState
中 initializeUpdateQueue()
:fiber.updateQueue
初始化(updateQueue
也是一个非常普通的对象具有一些特定的属性)
function createContainer() {
return createFiberRoot(...);
}
function createFiberRoot(...) {
var root = new FiberRootNode(...);
var uninitializedFiber = createHostRootFiber(tag, isStrictMode);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
//...
var initialState = {...};
uninitializedFiber.memoizedState = initialState;
initializeUpdateQueue(uninitializedFiber);
return root;
}
function FiberRootNode(...) {
this.tag = tag;
this.containerInfo = containerInfo;
//...
}
function createHostRootFiber() {
return createFiber(HostRoot, null, null, mode);
}
var createFiber = function (...) {
return new FiberNode(...);
}
双缓冲树是指在更新过程中,React会构建一个workInProgress树,当这个树构建完成后,会切换当前树指向它,从而提升渲染效率,避免中间状态导致的UI闪烁
React中的Fiber树结构,每个Fiber节点都有一个alternate属性,指向对应的另一个树的节点,这样在更新时就可以交替使用
双缓冲树的工作流程
- 构建阶段:React 在 workInProgress 树上进行异步渲染(Concurrent Mode)。
- 提交准备:当 workInProgress 树构建完成(finishedWork),进入提交阶段。
- DOM 更新:在 Mutation Phase 应用所有 DOM 变更。
- 切换指针:通过 root.current = finishedWork 将 current 树指向新树。
- 回调执行:在 Layout Phase 触发副作用回调,此时新树已生效。