整个render
阶段,就是通过当前的Fiber树
(以下统称current
树)遍历生成一棵workInProgress树
的过程,然后将需要更新渲染的Fiber
节点挂在FiberRoot
的finishWork
上
在遍历的过程中,会对每个节点进行调和,更新它自身的属性以及子节点,并打上更新标记
这个函数是整个render
阶段的入口,先上源码:
// render阶段的三个指针
let nextRoot: FiberRoot = null
let nextUnitOfWork: Fiber = null
let nextRenderExpirationTime: ExpirationTime = NoWork
function renderRoot(root: FiberRoot, isYieldy: boolean) {
// useEffect的调用
flushPassiveEffects()
isWorking = true
// 清空对 hook 的引用
const previousDispatcher = ReactCurrentDispatcher.current
ReactCurrentDispatcher.current = HooksDispatcherOnEmpty
const expirationTime = root.nextExpirationTimeToWorkOn
// 上一个任务因为时间片用完了而中断了,这个时候 nextUnitOfWork 是有工作的,
// 到了下一个时间切片,中途没有新的任务进来,那么这些全局变量都没有变过
// 而如果有新的更新进来,则势必 nextExpirationTimeToWorkOn 或者 root 会变化,那么肯定需要重置变量,并从头开始
if (expirationTime !== nextRenderExpirationTime || root !== nextRoot || nextUnitOfWork === null) {
resetStack()
nextRoot = root
nextRenderExpirationTime = expirationTime
nextUnitOfWork = createWorkInProgress(nextRoot.current, null)
root.pendingCommitExpirationTime = NoWork
}
let didFatal: boolean = false
do {
try {
workLoop(isYieldy)
} catch (thrownValue) {
console.error(thrownValue)
resetContextDependences()
resetHooks()
if (nextUnitOfWork === null) {
// 不可预期的错误
didFatal = true
onUncaughtError(thrownValue)
} else {
const sourceFiber = nextUnitOfWork
const returnFiber = sourceFiber.return
if (returnFiber === null) {
didFatal = true
onUncaughtError(thrownValue)
} else {
throwException(root, returnFiber, sourceFiber, thrownValue, nextRenderExpirationTime) // 错误处理,没完成
nextUnitOfWork = completeUnitOfWork(sourceFiber)
continue
}
}
}
break
} while (true)
isWorking = false
ReactCurrentDispatcher.current = previousDispatcher
resetContextDependences()
resetHooks()
if (didFatal) {
nextRoot = null
root.finishedWork = null
return
}
// 如果中途被打断,并没有生成一棵完整的workInProgress树,所以要把root.finishWork清掉
if (nextUnitOfWork !== null) {
root.finishedWork = null
return
}
nextRoot = null
if (nextRenderDidError) {
// 错误处理
}
root.pendingCommitExpirationTime = expirationTime
root.finishedWork = root.current.alternate
}
在这个函数里,会通过createWorkInProgress()
克隆一个新节点,然后让nextUnitOfWork
指向这个节点,另外还有两个指针nextRoot
和nextRenderExpirationTime
,保存着当前render阶段的几个重要信息。
如果时间片不够中断了,又没有新的任务进来,下个时间切片的时候还是同样的任务,这时就可以通过这三个指针,快速定位到上次的工作点,立即开始。反之,如果被新的任务打断了,那么就必须从根节点重新遍历了
function workLoop(isYieldy: boolean) {
if (isYieldy) {
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
} else {
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
}
}
function performUnitOfWork(workInProgress: Fiber): Fiber {
const current = workInProgress.alternate
let next: Fiber = null
// 返回它的子节点
next = beginWork(current, workInProgress, nextRenderExpirationTime)
workInProgress.memoizedProps = workInProgress.pendingProps
if (next === null) {
// 返回它的兄弟节点
next = completeUnitOfWork(workInProgress)
}
return next
}
在workLoop
中,会调用performUnitOfWork()
处理每个节点,而具体的处理逻辑是beginWork
和completeUnitOfWork
实现的,其中,beginWork
采用了树的前序遍历,completeUnitOfWork
采用后续遍历,从而保证每个树节点都会先进行beginWork
处理再进行completeUnitOfWork
处理
每次生成新的workInProgress
都会返回到workLoop
,更新nextUnitOfWork
的指向,如果nextUnitOfWork === null
,说明整棵workInProgress
树都已经生成,可以进入commit
阶段,异步时会追加一个shouldYield
的判断,如果时间不够用,则停止整个树的遍历,结束掉render阶段