React源码2 Renderer渲染器
什么是渲染器
在React架构这篇文章中已经介绍过了,所谓渲染器就是专门把虚拟DOM节点转成真实DOM节点的公共方法。
render
阶段始于performSyncWorkOnRoot
(同步更新)或performConcurrentWorkOnRoot
(异步更新)方法。
两个方法最终调用到workLoopSync
或workLoopConcurrent
两个函数,如下:1
2
3
4
5
6
7
8
9
10
11
12// 同步更新
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
// 异步更新
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
从代码中我们可以看出,更新过程中都会判断workInProgress
,而异步更新会调用shouldYield()
函数的判断
shouldYield
异步任务在处理的时候会调用shouldYield
,shouldYield
会判断是不是已经超时了,超时暂时先不做。1
2
3
4
5
6
7
8
9
10
11
12function shouldYield() {
if (deadline === null) {
return false;
}
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
// Disregard deadline.didTimeout. Only expired work should be flushed
// during a timeout. This path is only hit for non-expired work.
return false;
}
deadlineDidExpire = true;
return true;
}
performUnitOfWork
performUnitOfWork
函数最主要的功能就是调用beginWork()
和 completeUnitOfWork()
两个函数。beginWork()
为捕获阶段,此阶段会采取深度优先的方式遍历节点,并完成Fiber树创建以及diff算法。completeUnitOfWork()
为冒泡阶段,此阶段要完成生命周期(部分)的调用,形成effectlist
等
递归遍历
前面也讲过,渲染的时候需要调用递归遍历虚拟节点。那么在“递”和“归”的过程中,具体要做哪些工作呢?
“递”阶段
递阶段会从Fiber的RootFiber
开始深度优先遍历,为遍历到的每一个方法调用beginWork
方法。
beginWork
的工作内容是往传入的Fiber节点上添加子节点。并使两个节点连接起来。
1 | function beginWork( |
当第一次渲染,会根据 workInProgress.tag
来根据组件类型分别处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30switch (workInProgress.tag) {
case IndeterminateComponent:
// ...省略
case LazyComponent:
// ...省略
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent:
// ...省略
case HostRoot:
// ...省略
case HostComponent:
// ...省略
case HostText:
// ...省略
// ...省略其他类型
}
这里注意 updateFunctionComponent
方法,并不是真正的把真实的dom节点渲染到浏览器上,而是先做个标记,到commit
阶段再统一做渲染。
current
用来区分是 mount
还是 创建还是更新。如果当前Fiber
不存在的话,就会创建一个新的Fiber
节点。
“归”阶段
在“归”阶段会调用completeWork (opens new window)
处理Fiber
节点。
当某个Fiber
节点执行完completeWork
,如果其存在兄弟Fiber
节点(即fiber.sibling !== null)
,会进入其兄弟Fiber
的“递”阶段。
如果不存在兄弟Fiber
,会进入父级Fiber
的“归”阶段。
“递”和“归”阶段会交错执行直到“归”到rootFiber
。至此,render阶段的工作就结束了。
1 | if (next === null) { |
这个时候会调用 completeUnitOfWork
方法,此方法的作用是处理标记的EffectList
1 | // 执行effect相关 |