React源码2 Renderer渲染器

什么是渲染器

React架构这篇文章中已经介绍过了,所谓渲染器就是专门把虚拟DOM节点转成真实DOM节点的公共方法。

render阶段始于performSyncWorkOnRoot(同步更新)或performConcurrentWorkOnRoot(异步更新)方法。

两个方法最终调用到workLoopSyncworkLoopConcurrent两个函数,如下:

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

异步任务在处理的时候会调用shouldYieldshouldYield会判断是不是已经超时了,超时暂时先不做。

1
2
3
4
5
6
7
8
9
10
11
12
function 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
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
30
31
32
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const updateLanes = workInProgress.lanes;

if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;

if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// 如果实现因热重载而改变,则强制重新渲染
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// ...更新阶段的处理
} else if (!includesSomeLane(renderLanes, updateLanes)) {
//
//...
} else {
if ((current.effectTag & ForceUpdateForLegacySuspense) !== NoEffect) {

didReceiveUpdate = false;
}
}
} else {
/* 该didReceiveUpdate变量代表本次更新中本Fiber节点是否有变化 */
didReceiveUpdate = false;
}
}

当第一次渲染,会根据 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
30
switch (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
2
3
4
5
6
7
if (next === null) {
// workInProgress已经不存在子树,就开始进行"归"阶段
completeUnitOfWork(unitOfWork);
} else {
// next是beginWork调用后的返回值workInProgress.child
workInProgress = next;
}

这个时候会调用 completeUnitOfWork 方法,此方法的作用是处理标记的EffectList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 执行effect相关
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;
}

if (effectTag > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}