Appearance
render 阶段
对于组件,我们一般可以将其简单分为三种状态: mount(初次渲染)、update(更新)、unMount(卸载),那么对于一个组件的 render 阶段其在这三种状态下分别做哪些工作?
Mount(初次渲染)
组件的初次渲染一般发生于两种情况: ReactDOM.render()、更新时初次加载。其都是一套流程,下面我们主要按照 ReactDOM.render()的过程来说明。
对于 ReactDOM.render()的时候,其在 legacyRenderSubtreeIntoContainer()
的时候初始化生成了 FiberRoot 和 FiberRootNode 两个对象,并通过 updateContainer() 将整个 render 的流程放到任务调度中去,(对于 updateContianer 我们可以看 updateContainer 的说明),最终其执行的是 scheduleUpdateOnFiber(current, lane, eventTime) ,
scheduleUpdateOnFiber(fiber: Fiber, lane : Lane , eventTime : number )
项目初次渲染(render)或者触发更新的主要入口函数。
- ReactDOM.render()的时候 fiber 是 FiberRootNode
- 组件触发更新(如 setState)的时候 fiber 是 组件的 FiberNode
其主要流程分为以下几个过程
checkForNestedUpdates()
排除无限嵌套更新的问题检查工作,主要怕开发人员在组件更新的时候产生无限更新。 如
useEffect(() => { setVisible(!visible) } ,[ visible ])
更新优先级及找到 FiberRootNode
在当前 FiberNode 实例的 lanes 和 所有父节点的 childLanes 中添加当前 Update.lane
当组件触发更新的时候 可以通过 FiberNode.childLanes 找到当前需要更新节点线,以便在 beginWork 的时候作为判断 FiberNode 是否可以复用的依据。
缓存当前 Update 的优先级到 FiberNode.lanes 上,可以在调度的时候判断当前优先级下是否是该组件触发的更新
通过 FiberNode.return 向上找到根 FiberRoot
因为 根 FiberRootNode.tag === HostRoot ,然后通过
FiberRootNode.stateNode
找到FiberRoot
完成节点之后赋值 effect 链
markRootUpdated(root, lane, eventTime)
标记更新在根 FiberRoot 上标记当前存在此优先级的 Update
主要通过二级制位的方式在
根FiberRoot.pendingLanes
上标记当前存在此优先级的更新,如 setState 的优先级为0b0000000000000000000000000000001
,那么通过按位与的方式就可以在 pendingLanes 缓存root.pendingLanes |= updateLane;
这样 pendingLanes 的结果就变成0b????????????????????1
优先级 及 不同环境 的处理
js
// 最高优先级
if (lane === SyncLane) {
if (
// Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
schedulePendingInteractions(root, lane)
// 这是一个遗留的边界情况。reactdom .render的初始装入batchedUpdates内部的根应该是同步的,但是布局更新应该延迟到批处理结束。
performSyncWorkOnRoot(root)
} else {
ensureRootIsScheduled(root, eventTime)
schedulePendingInteractions(root, lane)
if (executionContext === NoContext) {
resetRenderTimer()
flushSyncCallbackQueue()
}
}
} else {
ensureRootIsScheduled(root, eventTime)
schedulePendingInteractions(root, lane)
}
可以看出对于所有的更新或者初始渲染主要分为两种:
- performSyncWorkOnRoot(root) 为核心的同步处理流程
- ensureRootIsScheduled(root, eventTime); 为核心的异步处理流程
问题: 为什么 ReactDOM.render 触发的首次渲染是一个同步的过程呢?不是说在新的 Fiber 架构下,render 阶段是一个可打断的异步过程。
-
下面我们看 performSyncWorkOnRoot ,因为对于 异步处理流程(ensureRootIsScheduled)其主要还是调用 performSyncWorkOnRoot 处理。
performSyncWorkOnRoot(root)
这是不经过 Scheduler 的同步任务的入口点。
从上图可以看出对于 performSyncWorkOnRoot 其主要工作分为以下几个
- effect 的处理
- 获取更新优先级并进入 render 阶段
- 对 render 阶段错误处理
- commit 阶段
- ensureRootIsScheduled(root, now())
renderRootSync(root: FiberRoot, lanes: Lanes)
主要工作为
prepareFreshStack(root, lanes)
重置调度队列。
此方法主要做的时候进行
workInProgressRoot 的缓存。
回退上次 render 未完成的 FiberNode 的堆栈数据
如果此次更新未完成的时候就因为进程需要交付给渲染流程,那么下次恢复 render 的时候,因为 workInProgress 是一个全局变量不为空,所以需要向上遍历对整条 FiberNode 进行堆栈信息的回退
js
// workInProgress 代表当前正在更新的Fibre对象
if (workInProgress !== null) {
// 向上遍历当前 fiber 的父节点,执行 unwindInterruptedWork 操作
// unwindInterruptedWork 用于回退一些堆栈信息
let interruptedWork = workInProgress.return
while (interruptedWork !== null) {
unwindInterruptedWork(interruptedWork)
interruptedWork = interruptedWork.return
}
}
- **workInProgress 的初始化 **(基于 root)
为什么需要进行prepareFreshStack
的流程
- 初次渲染 , workInProgressRoot 为 null
- 恢复 render 流程 , 异步处理流程中当渲染处理完毕,进程空闲的时候,这时候如果存在 Update 那么就会再次进行 render,这时候如果遇到高优先级的使得渲染优先级改变,那么这时候也需要进行 render 的初始化过程
workLoopSync()
基于prepareFreshStack
的过程中基于 root 创建一个一个根 FiberNode 的 workInProgress 和 workInProgress.alternate(current,上一次的 FiberNode 对象)进行双链比较处理
重置工作
js
function renderRootSync(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext
// 切换到渲染上下文
executionContext |= RenderContext
const prevDispatcher = pushDispatcher()
// 上下文的重置工作
resetContextDependencies()
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>))
}
executionContext = prevExecutionContext
popDispatcher(prevDispatcher)
if (enableSchedulingProfiler) {
markRenderStopped()
}
// Set this to null to indicate there's no in-progress render.
// 在 prepareFreshStack(root, lanes); 根据 root 创建了 workInProgressRoot 及 其他属性,这时候也进行重置工作
workInProgressRoot = null
workInProgressRootRenderLanes = NoLanes
return workInProgressRootExitStatus
}
workLoop()
在 renderRootSync 的时候我们根据 root 创建了一个 workInProgress 对象(全局对象),那么在 workLoopSync()或者workLoopConcurrent()
的过程中都是对当前的 workInProgress 对象进行一系列的操作。
js
// 同步的流程
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress)
}
}
/** @noinline */
// 异步的流程
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress)
}
}
从上面可以发现 我们本质上都是通过performUnitOfWork(workInProgress);
去构建 FiberNode 树,对于同步、异步的区别当前只是通过!shouldYield()
去判断我们是否需要继续下去。
performUnitOfWork(workInProgress)
真正区分 beginWork 和 completeUnitOfWork 的过程
我们先简单看一下源码
js
function performUnitOfWork(unitOfWork: Fiber): void {
// 获取其原始的 FiberNode 对象,在update 的时候可以通过 unitOfWork(workInProgress|新FiberNode) 和 current(旧FiberNode)的对比来判断是否可以进行复用等
const current = unitOfWork.alternate
let next
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
// 进行beginWork的流程
next = beginWork(current, unitOfWork, subtreeRenderLanes)
} else {
next = beginWork(current, unitOfWork, subtreeRenderLanes)
}
unitOfWork.memoizedProps = unitOfWork.pendingProps
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork)
} else {
workInProgress = next
}
ReactCurrentOwner.current = null
}
其实从performUnitOfWork的源码中我们看不出上图所示的构建流程,在performUnitOfWork中我们只能看出
beginWork 返回的 next 为 null 的时候会进行 completeUnitOfWork(unitOfWork)的过程,不为 null 的时候 赋值给 workInProgress 从而再次进入 do 循环。那么
next 是什么?
先简单看 beginWork 的过程,可见 next 指的是子节点。
jsfunction beginWork( current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,): Fiber | null { switch (workInProgress.tag) { return updateBlock(current, workInProgress, block, props, renderLanes); } } function updateBlock<Props, Data>( current: Fiber | null, workInProgress: Fiber, block: BlockComponent<Props, Data>, nextProps: any, renderLanes: Lanes, ) { // 返回的是当前FiberNode的child return workInProgress.child; }
对于没有子的兄弟节点怎么处理
对于上图的 H1 节点 其 next 为 null 那么就行进入
completeUnitOfWork(unitOfWork);
执行complteWork
过程,可见对于上图的4 beginWork --> 5 complteWork
是正确的,那么怎么知道下一步处理兄弟节点 div 呐?jsfunction completeUnitOfWork(unitOfWork: Fiber): void { // Attempt to complete the current unit of work, then move to the next // sibling. If there are no more siblings, return to the parent fiber. let completedWork = unitOfWork do { const current = completedWork.alternate const returnFiber = completedWork.return // 如果存在兄弟节点 那么将兄弟节点赋给workInProgress 进入到 兄弟节点的 workLoop 的流程 const siblingFiber = completedWork.sibling if (siblingFiber !== null) { // If there is more work to do in this returnFiber, do that next. workInProgress = siblingFiber return } // Otherwise, return to the parent // 如果 不存在兄弟节点 那么通过 do{} 循环执行 父节点的 completeWork流程 // 此时 completedWork指向了父节点,那么在父节点完成 completeWork流程 时也会判断其是否存在兄弟节点,这样形成一个深度优先的链表 遍历流程 completedWork = returnFiber // Update the next thing we're working on in case something throws. workInProgress = completedWork } while (completedWork !== null) }
可见真正形成向下 beginWork, 向上 completedWork 的过程是通过 performUnitOfWork() 和 completeUnitOfWork()
对于没有子没有兄弟了怎么进入父节点
对于 render 阶段,其最主要的工作就是构建 FiberNode 树,