fiber树构造(初次创建)
- fiber树构造的2种情况:
- 1.初次创建
- 在React应用首次启动时,界面还没有渲染
- 此时并不会进入对比过程,相当于直接构造一棵全新的树
- 2.对比更新
- React应用启动后,界面已经渲染
- 如果再次发生更新,创建新fiber之前需要和旧fiber进行对比
- 最后构造的fiber树有可能是全新的,也可能是部分更新的
- 1.初次创建
- 这里重点关注初次创建这种情况,主要突出fiber树构造过程
- 下面会在 Legacy 模式下进行分析
- 因为只讨论fiber树构造原理,Concurrent模式与Legacy没有区别
示例代码:
class App extends React.Component {
componentDidMount() {
console.log('App Mount');
console.log(`App组对应的fiber节点:`,this._reactInternals);
}
render() {
return (
<div className="app">
<header>header</header>
<Content />
</div>
);
}
}
class Content extends React.Component {
componentDidMount() {
console. log('Content Mount');
console.log(`Content组应的fiber节:`, this._reactInternals);
}
render() {
return(
<React. Fragment>
<p>1</p>
<p>2</p>
</React. Fragment>
);
}
}
export default App;
启动阶段
- 在前文分析了2种启动模式的差异,在进入 react-reconciler 包之前(调用 updateContainer 之前)
- 内存状态图如下:
-
根据这个结构,可以在控制台中打出当前页面对应的fiber树(用于观察其结构):
document.getElementByld('root')._reactRootContainer._internalRoot.current;
-
然后进入react-reconciler包调用updateContainer函数:
// ... 省略了部分代码 export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?ReactSComponent<any, any>, callback: ?Function, ): Lane { // 获取当前时间戳 const current = container.current; const eventTime = requestEventTime(); // 1.创建一个优先级变量(车遵模型) const lane = requestUpdateLane(current); // 2.根据车道优先级,创建update对象,并加入fiber.updateQueue.pending队列 const update = createUpdate(eventTime, lane); update.payload = { element }; callback = callback === undefined ? null : callback; if (callback !== null) { update.callback = callback; } enqueueUpdate(current, update); // 3. 进入reconciler运作流程中的`输入环节 scheduleUpdateOnFiber(current, lane, eventTime); return lane; }
-
由于 update 对象的创建,此时的内存结构如下
- 注意
- 最初的ReactElement对象被挂载到
HostRootFiber.updateQueue.shared.pending.payload.element
中,- 后面fiber树构造过程中会再次变动
构造阶段
- 为了突出构造过程,排除干扰,先把内存状态图中的 FiberRoot 和 HostRootFiber 单独提出来
-
在 scheduleUpdateOnFiber 函数中
//...省略部分代码 export function scheduleUpdateOnFiber( fiber: Fiber, lane:Lane, eventTime: number, ) { // 标记优先级 const root = markUpdateLaneFromFiberToRoot(fiber, lane); if(lane === SyncLane) { if( (executionContext & LegacyUnbatchedContext) !== NoContext && (executionContext & (RenderContext CommitContext)) === NoContext ) { // 首次渲染,直接进行fiber构造 performSyncWorkOnRoot(root); } // ... } }
-
可以看到,在Legacy模式下且首次渲染时
-
有2个函数 markUpdateLaneFromFiberToRoot 和 performSyncWorkOnRoot
-
其中 markUpdateLaneFromFiberToRoot(fiber,lane)函数在fiber树构造(对比更新)中才会发挥作用
-
因为在初次创建时并没有与当前页面所对应的fiber树,所以核心代码并没有执行,最后直接返回了FiberRoot对象
-
performSyncWorkOnRoot
看起来源码很多,初次创建中真正用到的就2个函数:function performSyncWorkOnRoot(root) { let lanes; let exitStatus; if ( root === workInProgressRoot && includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes) ) { // 初次构造时(因为root = fiberRoot,workInProgressRoot=null),所以不会进入 } else { // 1. 获取本次render的优先级,初次构造返回 NoLanes lanes = getNextLanes(root, NoLanes); // 2. 从root节点开始,至上而下更新 exitStatus = renderRootSync(root, lanes); } // 将最新的fiber树挂载到root.finishedWork节点上 const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedlanes = lanes; // 进入commit阶段 commitRoot(root); //...后面的内容跳过 }
-
其中 getNextLanes 返回本次 render 的渲染优先级中
-
renderRootSync
function renderRootSync(root: FiberRoot, lanes: Lanes) { const prevExecutionContext = executionContext; executionContext |= RenderContext; // 如果fiberRoot变动,或者update.Lone变动,都会刷新栈帧,丢弃上一次渲染进度 if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) { // 刷新栈帧,legacy模式下都会进入 prepareFreshStack(root,lanes); } do { try { workLoopSync(); break; } catch (thrownValue) { handleError(root, thrownValue); } } while (true); executionContext = prevExecutionContext; // 重置全局变量,表明render束 workInProgressRoot = null; workInProgressRootRenderLanes = NoLanes; return workInProgressRootExitStatus; }
-
在 renderRootSync 中,在执行fiber树构造前(workLoopSync)会先刷新栈帧
-
prepareFreshStack 在这里创建了 HostRootFiber.alternate
-
重置局变量 workInProgress 和 workInProgressRoot 等
循环构造
-
逻辑来到 workLoopSync, 绥安本节在 Legacy 模式下进行讨论
-
此处还是对比一下 workLoopConcurrent
function workLoopSync() { while (workInProgress !== null) { performUnitOfWork(workInProgress); } } function workLoopConcurrent() { // Perform work until Scheduler asks us to yield while (workInProgress !== null && !shouldYield()) { performUnitOfwork(workInProgress); } }
-
可以看到workLoopConcurrent相比于Sync,会多一个停顿机制
-
这个机制实现了时间切片和可中断染
-
结合 performUnitOfWork 函数
// ...省略部分无关代码 function performUnitOfWork(unitofwork: Fiber): void { // unitOfWork 就是被传入的 workInProgress const current = unitOfWork.alternate; let next; next = beginWork(current, unitOfWork, subtreeRenderLanes); unitOfWork.memoizedProps = unitOfWork.pendingProps; if (next === null) { // 如果没有派生出新的节点,则进入completeWork阶段,传入的是当前unitOfWork completeUnitOfWork(unitOfWork); } else { workInProgress = next; } }
-
可以明显的看出,整个fiber树构造是一个深度优先遍历其中有2个重要的变量workInProgress和current(双缓冲技术)
- workInProgress 和 current 都视为指针
- workInProgress 指向当前正在构造的fiber节点
current = workInProgress.alternate
(即fiber.alternate), 指向当前页面正在使用的fiber节点.- 初次构造时,页面还未渲染,此时current = null
-
在深度优先遍历中,每个 fiber 节点都会经历2个阶段
- 1.探寻阶段 beginWork
- 2.回溯阶段 completeWork
-
这2个阶段共同完成了每一个fiber节点的创建,所有fiber节点则构成了fiber树.
探寻阶段 beginWork
-
beginWork(current, unitOfWork, subtreeRenderLanes)
-
针对所有的 Fiber 类型,其中的每一个 case 处理一种 Fiber 类型.
-
updateXXX函数(如:updateHostRoot, updateClassComponent等)的主要逻辑:
-
1.根据 ReactElement对象创建所有的fiber节点,最终构造出fiber树形结构(设置 return和sibling指针)
-
2.设置 fiber.flags (二进制形式变量,用来标记 fiber节点的增,删,改状态,等待completeWork阶段处理)
-
3.设置 fiber.stateNode 局部状态(如Class类型节点:fiber.stateNode=new Class())
function beginwork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { const updateLanes = workInProgress.lanes; if (current !== null) { // update逻辑,首次render不会进入 } else { didReceiveUpdate = false; } // 1.设置workInProgress优先级为NoLanes(最高优先级) workInProgress.lanes = NoLanes; // 2.根据workInProgress节点的类型,用不同的方法派生出子节点 switch ( workInProgress.tag //了本例使用到case ) { case ClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress. pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateclassComponent( current, workInProgress, Component, resolvedProps, renderLanes, ); } case HostRoot: return updateHostRoot(current, workInProgress, renderLanes); case HostComponent: return updateHostComponent(current, workInProgress, renderLanes); case HostText: return updateHostText(current, workInProgress); case Fragment: return updateFragment(current, workInProgress, renderLanes); } }
-
-
updateXXX函数(如: updateHostRoot, updateClassComponent等)虽然case较多
-
但是主要逻辑可以概括为3个步骤
- 1.根据fiber.pendingProps, fiber.updateQueue等输数据状态
- 计算fiber.memoizedState作为输出状态
- 2.获取下级ReactElement对
- a. class类型的fiber节点
- 构建 React.Component 实例
- 把新实例挂载到 fiber.stateNode 上
- 执行render之前的生命周期函数
- 执行render方法,获取下级 reactElement
- 根据实际情况,设置fiber.flags
- b.function 类型的 fiber 节点
- 执行function, 获取下级reactElement
- 根据实际情况,设置fiber.flags
- c. HostComponent类型(如: div, span, button等)的 fiber节点
- pendingProps.children作为下级 reactElement
- 如果下级节点是文本节点,则设置下级节点为null. 准备进入completeUnitOfWork阶段
- 根据实际情况设置fiber.flags
- d.其他类型
- a. class类型的fiber节点
- 3.根据ReactElement对象,调用reconcileChildren生成Fiber子节点(只生成次级子节点)
- 根据实际情况,设置fiber.flags
- 1.根据fiber.pendingProps, fiber.updateQueue等输数据状态
-
不同的updateXXX函数处理的fiber节点类型不同总的目的是为了向下生成子节点
-
在这个过程中把一些需要持久化的数据挂载到fiber节点上
-
如fiber.stateNode,fiber.memoizedState等把fiber节点的特殊操作设置到fiber.flags
-
如:节点ref,class组件的生命周期,function组件的hook,节点删除等
-
这里列出updateHostRoot,updateHostComponent的代码,对于其他常用case的分析
-
如class类型,function类型
-
fiber树的根节点是 HostRootFiber 节点
-
所以第一次进入beginWork会调用updateHostRoot(current, worklnProgress, renderLanes)
// 省略无关代码 function updateHostRoot(current, workInProgress, renderlanes) { // 1、状态计算,更新整合到workInProgress.memoizedState中来 const updateQueue = workInProgress.updateQueue; const nextProps = workInProgress.pendingProps; const prevState = workInProgress.memoizedState; const prevChildren = prevState !== null ? prevState.element : null; cloneUpdateQueue(current, workInProgress); //遍历updateQueue.shared.pending,提取有足够优先级的update对象,计算出最终的状态 workInProgres. processUpdateQueue(workInProgress, nextProps, null, renderLanes); const nextState = workInProgress.memoizedState; // 2.获取下级'ReactElement"对象 const nextChildren = nextState.element; const root: FiberRoot = workInProgress.stateNode; if (root.hydrate && enterHydrationState(workInProgress)) { //..服务端渲染相关,此处省路 } else { // 3.根据'ReactElement"对象,调用 reconcileChildren'生成"Fiber"子节点(只生成"次级子节点") reconcileChildren(current, workInProgress, nextChildren, renderLanes); } return workInProgress.child; }
-
普通DOM标签类型的节点(如div,span,p), 会进入 updateHostComponent:
// ...省略部分无关代码 function updateHostComponent( current: Fiber| null, workInProgres: Fiber, renderLanes: Lanes, ) { // 1. 状态计算,由于HostComponent是无状态组件,所以只需要收集 nextProps即可,它没有 memoizedState const type = workInProgress.type; const nextProps = workInProgress.pendingProps; const prevProps = current !== null ? current.memoizedProps : null; // 2. 获取下级ReactElement对象 let nextChildren = nextProps.children; const isDirectTextChild = shouldSetTextContent(type, nextProps); if(isDirectTextChild) { // 如果子节点只有一个文本节点,不用再创建一个HostText类型的fiber nextChildren = null; } else if (prevProps != null && shouldSetTextContent(type, prevProps)) { // 特殊操作需要设置fiber.flags workInProgress.flags |= ContentReset; } // 特殊操作需要设置fiber.flags markRef(current, workInProgress); // 3. 根据`ReactElement'对象,调用`reconcileChildren'生成Fiber`子节点(只生成`次级子节点`) reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; }
回溯阶段 completeWork
-
completeUnitOfWork(unitOfWork) 处理 beginWork 阶段已经创建出来的 fiber 节点
-
核心逻辑
-
1.调用completeWork
- 给fiber节点(tag=HostComponent, HostText)创建DOM实例(内存中)
- 设置 fiber.stateNode局部状态(如 tag=HostComponent, HostText节点: fiber.stateNode指向这个DOM实例).
- 为DOM节点设置属性,绑定事件(涉及合成事件)
- 设置fiber.flags标记
-
2.把当前fiber对象的副作用队列(firstEffect 和 lastEffect)添加到父节点的副作用队列之后
- 更新父节点的firstEffect和lastEffect指针
-
3.识别beginWork阶段设置的fiber.flags
- 判断当前fiber是否有副作用(增,删,改)
- 如果有,需要将当前fiber加入到节点的 effects 队列,等commit阶段处理
function completeUnitOfWork(unitOfWork: Fiber): void { let completedWork = unitOfWork; // 外层循环控制并移动指针(`workInProgress',`completedWork"等) do { const current = completedWork.alternate; const returnFiber = completedWork.return; if ((completedWork.flags & Incomplete) === NoFlags) { let next; // 1.处理Fiber节点,会调用渲染器(调用react-dom包,关联Fiber节点和dom对象,绑定事件等) next = completeWork(current, completedWork, subtreeRenderLanes); //处理单个节点 if (next !== null) { // 如果派生出其他的子节点,则回到`beginWork"阶段进行处理 workInProgress = next; return; } // 重置子节点的优先级 resetChildLanes(completedWork); if ( returnFiber !== null && (returnFiber.flags & Incomplete) === NoFlags ) { // 2.收集当前Fiber节点以及其子树的副作用effects // 2.1把子节点的副作用队列添加到父节点上 if (returnFiber.firstEffect == null) { returnFiber.firstEffect = completedWork.firstEffect; } if (completedWork.lastEffect !== null) { if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = completedWork.firstEffect; } returnFiber.lastEffect = completedWork.lastEffect; } // 2.2如果当前fiber节点有副作用,将其添加到子节点的副作用队列之后. const flags = completeWork.flags; if (flags > PerformedWork) { // PerformedWork是提供给 React DevTools读取的,所以略过PerformedWork if (returnFiber.lastEffect !== null){ returnFiber.lastEffect.nextEffect = completedWork; } else { returnFiber.firstEffect = completedWork; } returnFiber.lastEffect= completedWork; } } else { // 异常处理,本节不讨论 } const siblingFiber = completedWork.sibling; if (siblingFiber !== null) { // 如果有兄弟节点,返回之后再次进入`beginWork`阶段 workInProgress = siblingFiber; return; } // 移动指针,指向下一个节点 completedWork = returnFiber; workInProgress = completedWork; } while (completedWork !== null); // 已回溯到根节点,设置workInProgressRootExitStatus = RootCompleted if (workInProgressRootExitStatus === RootIncomplete) { workInProgressRootExitStatus = RootCompleted; } }
-
-
fiber 处理函数 completeWork
function completeWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { const newProps = workInProgress.pendingProps; switch(workInProgress.tag) { case ClassComponent: { // Class类型不做处理 return null; } case HotRoot: { const fiberRoot = (workInProgress.stateNode: FiberRoot); if (fiberRoot.pendingContext) { fiberRoot.context = fiberRoot.pendingContext; fiberRoot.pendingContext= null; } if (current === null || current.child == null){ // 设置fiber.flags记 workInProgress.flags = Snapshot; } return null; } case HostComponent: { popHostContext(workInProgress); const rootContainerInstance = getRootHostContainer(); const type = workInProgress.type; if (current !== null && workInProgress.stateNode !== null) { // update逻辑,初次render不会进入 } else { const currentHostContext = getHostContext(); // 1.创建DOM对象 const instance = createInstance( type, newProps, rootContainerInstance, currentHostContext, workInProgress, ); // 2、把子树中的DOM对象append到本节点的DOM对象之后 appendAllChildren(instance, workInProgress, false, false); // 设置stateNode届性,指向DOM对象 workInProgress.stateNode = instance; if( // 3,设置DOM对象的属性,绑定事件等 finalizeInitialChildren( instance, type, newProps, rootContainerInstance, currentHostContext, ) ) { // 设置fiber. flags标(Update) markUpdate(workInProgress); } if (workInProgress.ref !== null) { //设置fiber.flags标(Ref) markRef(workInProgress); } return null; } } } }
-
可以看到在满足条件的时候也会设置 fiber.flags, 所以设置 fiber.flags 并非只在 beginWork 阶段
过程图解
- 基于一个小例子来概述
class App extends React.Component { componentDidMount() { console.log('App Mount'); console.log(`App组对应的fiber节点:`, this._reactInternals); } render() { return( <div className="app"> <header>header</header> <Content /> </div> ); } } class Content extends React.Component { componentDidMount() { console. log('Content Mount'); console.log(`Content组应的fiber节点:`, this._reactInternals); } render() { return( <React.Fragment> <p>1</p> <p>2</p> </React.Fragment> ); } } export default App;
- 针对本节的示例代码,将整个fiber树构造过程表示出来:
- 将整个fiber树构造过程用图表示出来
- 构造前:
- 进入循环构造前会调用prepareFreshstack刷新栈帧
- 在进入fiber树构造循环之前,保持这个初始化状态
- performUnitOfWork 第1次调用(只执行 beginWork):
- 执行前
- workInProgress 指针指向 HostRootFiber.alternate 对象
- 此时 current = workInProgress.alternate 指向 fiberRoot.current 是非空的
- 初次构造,只在根节点时,current 非空
- 执行过程
- 调用 updateHostRoot
- 在 reconcileChildren 阶段
- 向下构造次级子节点
fiber(<App/>)
, 同时设置子节点(fiber(<App/>))
fiber.flags |= Placement
- 执行后
- 返回下级节点
fiber(<App/>)
- 移动 workInProgress 指针指向子节点
fiber(<App/>)
- 返回下级节点
- 执行前
- performUnitOfWork第2调用 (beginWork):
- 执行前:
- workInProgress 指针指向
fiber(<App/>)
节点,此时 current = null
- workInProgress 指针指向
- 执行过程:
- 调用 updateClassComponent
- 本示例中,class实例存在生命周期函数componentDidMount,
- 所以会设置
fiber(<App/>)
节点,workInProgress.flags |= Update
- 另外也会为了 React DevTools 能够识别状态组件的执行进度,会设置
workInProgress.flags |= PerformedWork
- 在commit阶段会排除这个 flag, 此处只是列出 workInProgress.flags 的设置场景, 不讨论 React DevTools
- 需要注意
classInstance.render()
在本步骤执行后,虽然返回了 render 方法中所有的ReactElement对象 - 但是随后 reconcileChildren 只构造次级子节点
- 在 reconcileChildren 阶段,向下构造次级子节点div
- 执行后:
- 返回下级节点fiber(div)
- 移动 workInProgress 指针指向子节点fiber(div)
- 执行前:
- performUnitofwork第3次调用(只执行 beginWork):
- 执行前:
- workInProgress指针指向fiber(div)节点,此时 current=null
- 执行过程:
- 调用updateHostComponent
- 在reconcileChildren阶段,向下构造次级子节点(本示例中,div有2个次级子节点)
- 执行后:
- 返回下级节点fiber(header), 移动 workInProgress 指针指向子节点fiber(header)
- 执行前:
- performUnitOfwork第4次调用(行 beginWork和 completeUnitOfWork):
- beginWork执行前:
- workInProgress指针指向 fiber(header)节点,此时 current = null
- beginWork行过程:
- 调用updateHostComponent
- 本示例中header的子节点是一个直接文本节点,设置nextChildren=null
- 直接文本节点并不会被当成具体的fiber节点进行处理
- 而是在宿主环境(父组件)中通过属性进行设置
- 所以无需创建HostText类型的fiber节点,同时节省了向下遍历开销
- 由于 nextChildren = null, 经过 reconcileChildren 阶段处理后,返回值也是null
- beginWork执行后:
- 由于下级节点为null, 所以进入completeUnitOfWork(unitOfWork)函数
- 传入的参数unitOfWork实际上就是 workInProgress(此时指向 fiber(header)节点)
- beginWork执行前:
-
completeUnitOfWork 执行前:
- workInProgress 指针指向 fiber(header)节点
-
completeUnitOfWork 执行过程:
- 以fiber(header)为起点,向上回溯
-
第1次循环
- 1.执行 completeWork 函数
- 创建fiber(header)节点对应的DOM实例,并append子节点的DOM实例(在内存中)
- 设置DOM属性,绑定事件等(本示例中,节点fiber(header)没有事件绑定)
- 2.上移副作用队列
- 由于本节点fiber(header)没有副作用(fiber.flags=0)
- 所以执行之后副作用队列没有实质变化(目前为空)
- 3.向上回溯
- 由于还有兄弟节点,把workInProgress指针指向下一个兄弟节点
fiber(<Content/>)
- 退出 completeUnitOfWork
- 由于还有兄弟节点,把workInProgress指针指向下一个兄弟节点
- 1.执行 completeWork 函数
- performUnitOfWork第5次(beginWork):
- 执行前:workInProgress 指针指向
fiber(<Content/>)
节点 - 执行过程:这是一个
class
类型的节点,与第2次调用逻辑一致 - 执行后:返回下级节点
fiber(p)
,移动workInProgress
指针指向子节点fiber(p)
- 执行前:workInProgress 指针指向
- performUnitOfWork 第6次调用(执行 beginWork和 completeUnitOfwork)
- 与第4次调用中创建fiber(header)节点的逻辑一致.先后会执行beginWork和completeUnitOfWork
- 最后构造DOM实例,并将把workInProgress指针指向下一个兄弟节点fiber§
- performUnitOfwork第7次调用(执行 beginWork 和 completeUnitOfWork)
- beginwork执行过程
- 与上次调用中创建fiber§节点的逻辑一致
- completeUnitOfWork执行过程
- 以fiber§为起点,向上回溯
- 第1次循环
- 1.执行completeWork函数:
- 创建fiber§节点对应的DOM实例,并append子树节点的DOM实例
- 2.上移副作用队列:
- 由于本节点fiber§没有副作用,所以执行之后副作用队列没有实质变化(目前为空)
- 3.向上回溯:
- 由于没有兄弟节点,把workInProgress指针指向父节点
fiber(<Content/>)
- 由于没有兄弟节点,把workInProgress指针指向父节点
- 1.执行completeWork函数:
- 第2次循环:
- 1.执行completeWork函数:
- class类型的节点不做处理
- 2.上移副作用队列:
- 本节点
fiber(<Content/>)
的flags标志位有改动( completedWork.flags > PerformedWork) - 将本节点添加到父节点(fiber(div))的副作用队列之后
- firstEffect和lastEffect属性分别指向副作用队列的首部和尾部
- 本节点
- 3.向上回溯:
- 把workInProgress指针指向节点fiber(div)
- 1.执行completeWork函数:
- 第3次循环:
- 1.执行completeWork函数:
- 创建
fiber(div)
节点对应的DOM实例,并append子树节点的DOM实例
- 创建
- 2.上移副作用队列:
- 本节点
fiber(div)
的副作用队列不为空,将其拼接到父节点fiber<App/>
的副作用队列后面
- 本节点
- 3.向上回溯:
- 把workInProgress指针指向父节点
fiber(<App/>)
- 把workInProgress指针指向父节点
- 1.执行completeWork函数:
- 第4次循环:
- 1.执行completework函数:class类型的节点不做处理
- 2.上移副作用队列:
- 本节点
fiber(<App/>)
的副作用队列不为空 - 将其拼接到父节点
fiber(HostRootFiber)
的副作用队列上 - 本节点
fiber(<App/>)
的flags标志位有改动(completedWork.flags > Performedwork) - 将本节点添加到父节点fiber(HostRootFiber)的副作用队列之后
- 最后队列的顺序(Effect顺序)是子节点在前,本节点在后
- 本节点
- 3.向上回溯:
- 把workInProgress指针指向父节点
fiber(HostRootFiber)
- 把workInProgress指针指向父节点
- 第5次循环:
- 1.执行completeWork函数:
- 对于 HostRoot类型的节点,初次构造时设置
workinProgress.flags |= Snapshot
- 2.向上回溯:
- 由于父节点为空,无需进入处理副作用队列的逻辑
- 最后设置
workInProgress = null
- 并退出 completeUnitOfWork
- 1.执行completeWork函数:
- 到此整个fiber树构造循环已经执行完毕,拥有一棵完整的fiber树
- 并且在fiber树的根节点上挂载了副作用队列,副作用队列的顺序是层级越深子节点越靠前
- renderRootsync函数退出之前,会重置
workInProgressRoot = null
- 表明没有正在进行中的 render,且把最新的fiber树挂载到
fiberRoot.finishedwork
上 - 这时整个fiber树的内存结构如下
- 注意fiberRoot.finishedwork和fiberRoot.current指针,在commitRoot阶段会进行处理
总结
- 首先我们有一个探寻的过程,首先探寻到App,然后从App到div,再到 header,发现没有子元元素了
- 所以到 Content,到Content之前会做一件事情,就是把它自己需要的dom,在内存中创建出来
- 包括包含的哪些属性也创建出来,生成一个effect的一个属性,合并到div里面去
- 这个就是说白了,探寻到header, header进行回溯,我在内存中创建了一些东西
- 因为这个在更新的时候要用,总得找个地方存起来,存起来的东西就叫做 effect
- 存在哪里呢?存在它的父级,即它的return属性,就是它的父级 div 这里
- header没有子元素,但是有兄弟节点,接下来回溯到 Content
- 又开始继续往下去探寻,看到了 p,这个时候 p 没有子元素了
- 这个时候p在回溯之前,也会跟 header 一样, 把跟自己相关的 dom 元素创建出来
- 创建完之后,打一个 flag,然后搞一个 effect,因为它得把它自己要怎么更新
- 以及更新哪些内容给它记录下来,就是通过这个effect
- effect 是一个链表, 这个就是在后面 commit 阶段真正渲染的时候要用的
- 之后,这个p标签就把它的effect也给合并到Content的effect的链表里面
- 接下来p完成之后开始回溯, 发现它有兄弟结点也是个p,这个p跟前面的一样操作
- 最后这个p把自己的一些东西生成在内存中, 生成之后搞一个 effect
- 然后合并到 Content,也就是它的父元素里面
- 最后的这个p开始继续回溯,回到了Content,Content 自己也要回溯
- Content 自己只是需要做一些事情,就是给自己打一些标记,打一些flag
- 因为 Content 自己也有一些 effect,之前两个p标签的effect都合并到了 Content 里
- 这个时候 Content 会把它的effect合并到div里,之后回溯到div,也同样重复这类操作
- 之后回溯到App, 继续重复此类操作:打flag, 生成dom,将自己effect合并到上级effect链表
- 最后从App回溯到HostRootFiber, 此时我们的effect 链表已经特别庞大了
- 这个effect的链表包含我们整棵表树更新的信息,包含怎么更新,更新的内容是什么,DOM元素是哪些等
- 所以最后Fiber树创建完成之后,就带着这个effect链表
- 接下来就进行我们页面的真正的渲染环节,总体来说,和React16版本的流程,区别不大