课程地址 https://xiaochen1024.com/courseware/60b1b2f6cf10a4003b634718/60b1b348cf10a4003b634720
- 捕获阶段 从根节点rootFiber开始,遍历到叶子节点,每次遍历到的节点都会执行beginWork,并且传入当前Fiber节点,然后创建或复用它的子Fiber节点,并赋值给workInProgress.child。
- 冒泡阶段 在捕获阶段遍历到子节点之后,会执行completeWork方法,执行完成之后会判断此节点的兄弟节点存不存在,如果存在就会为兄弟节点执行completeWork,当全部兄弟节点执行完之后,会向上‘冒泡’到父节点执行completeWork,直到rootFiber。最后形成一颗Fiber树,每个节点以child和return相连。
beginWork主要的工作是创建或复用子fiber节点
function beginWork(
current: Fiber | null,//当前存在于dom树中对应的Fiber树
workInProgress: Fiber,//正在构建的Fiber树
renderLanes: Lanes,//第12章在讲
): Fiber | null {
// 1.update时满足条件即可复用current fiber进入bailoutOnAlreadyFinishedWork函数
//2.根据tag来创建不同的fiber 最后进入reconcileChildren函数
}
在首次渲染时除了rootFiber外,current 等于 null,因为首次渲染dom还没构建出来
,在update时current不等于 null,因为update时dom树已经存在了,所以beginWork函数中用current === null来判断是mount还是update进入不同的逻辑
- mount:根据fiber.tag进入不同fiber的创建函数,
最后都会调用到reconcileChildren创建子Fiber
- update:在构建workInProgress的时候,当满足条件时,会
复用current Fiber来进行优化
,也就是进入bailoutOnAlreadyFinishedWork
的逻辑,能复用didReceiveUpdate变量是false,复用的条件是- oldProps === newProps && workInProgress.type === current.type 属性和fiber的type不变
- !includesSomeLane(renderLanes, updateLanes) 更新的优先级是否足够
创建子fiber的过程会进入reconcileChildren,该函数的作用是为workInProgress fiber节点生成它的child fiber即 workInProgress.child。然后继续深度优先遍历它的子节点执行相同的操作。
区分mount和update两种情况,进入reconcileChildFibers或mountChildFibers
var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);
function ChildReconciler(shouldTrackSideEffects) {
function placeChild(newFiber, lastPlacedIndex, newIndex) {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {//是否追踪副作用
// Noop.
return lastPlacedIndex;
}
var current = newFiber.alternate;
if (current !== null) {
var oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// This is a move.
newFiber.flags = Placement;
return lastPlacedIndex;
} else {
// This item can stay in place.
return oldIndex;
}
} else {
// This is an insertion.
newFiber.flags = Placement;
return lastPlacedIndex;
}
}
}
reconcileChildren与effectTag与dom commit 在ChildReconciler中用shouldTrackSideEffects来判断是否为对应的节点打上effectTag 例如如果一个节点需要进行插入操作,需要满足两个条件:
- fiber.stateNode!==null 即fiber存在真实dom,真实dom保存在stateNode上
- (fiber.effectTag & Placement) !== 0 fiber存在Placement的effectTag
为Fiber打上effectTag之后在commit阶段会被执行对应dom的增删改,而且在reconcileChildren的时候,rootFiber是存在alternate的,即rootFiber存在对应的current Fiber,所以rootFiber会走reconcileChildFibers的逻辑,所以shouldTrackSideEffects等于true会追踪副作用,最后为rootFiber打上Placement的effectTag,然后将dom一次性插入,提高性能。
如果进入了bailoutOnAlreadyFinishedWork复用的逻辑,会判断优先级,优先级足够则进入cloneChildFibers否则返回null
completeWork主要工作是处理fiber的props、创建dom、创建effectList
- 根据workInProgress.tag进入不同函数,我们以HostComponent举例
- update时(除了判断current===null外还需要判断workInProgress.stateNode===null),
调用updateHostComponent处理props(包括onClick、style、children ...),并将处理好的props赋值给updatePayload,最后会保存在workInProgress.updateQueue上
- mount时
调用createInstance创建dom,将后代dom节点插入刚创建的dom中,调用finalizeInitialChildren处理props(和updateHostComponent处理的逻辑类似)
在beginWork的mount时,rootFiber存在对应的current,所以他会执行mountChildFibers打上Placement的effectTag,在冒泡阶段也就是执行completeWork时,我们将子孙节点通过appendAllChildren挂载到新创建的dom节点上
,最后就可以一次性将内存中的节点用dom原生方法反应到真实dom中。
在beginWork 中我们知道有的节点被打上了effectTag的标记,有的没有,而在commit阶段时要遍历所有包含effectTag的Fiber来执行对应的增删改,那我们还需要从Fiber树中找到这些带effectTag的节点嘛,答案是不需要的,这里是以空间换时间,在执行completeWork的时候遇到了带effectTag的节点,会将这个节点加入一个叫effectList中
,所以在commit阶段只要遍历effectList就可以了
(rootFiber.firstEffect.nextEffect就可以访问带effectTag的Fiber了)
effectList的指针操作发生在completeUnitOfWork函数中
形成环状链表的时候会从触发更新的节点向上合并effectList直到rootFiber,这一过程发生在completeUnitOfWork函数中,整个函数的作用就是向上合并effectList
然后commitRoot(root);进入commit阶段