书接上节,我们接着来阐述ReactDom.render。

fiber的数据结构

每一个ReactElement都对应一个Fiber,setState和render都会生成新的Fiber树。这种架构有什么好处、以及它是如何由来,解决什么问题,可以参考这篇文章
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Fiber对应一个组件需要被处理或者已经处理了,一个组件可以有一个或者多个Fiber
type Fiber = {|
// 标记不同的组件类型
tag: WorkTag,

// ReactElement里面的key
key: null | string,

// ReactElement.type,也就是我们调用`createElement`的第一个参数
elementType: any,

// The resolved function/class/ associated with this fiber.
// 异步组件resolved之后返回的内容,一般是`function`或者`class`
type: any,

// The local state associated with this fiber.
// 跟当前Fiber相关本地状态(比如浏览器环境就是DOM节点)
stateNode: any,

// 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
return: Fiber | null,

// 单链表树结构
// 指向自己的第一个子节点
child: Fiber | null,
// 指向自己的兄弟结构
// 兄弟节点的return指向同一个父节点
sibling: Fiber | null,
index: number,

// ref属性
ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,

// 新的变动带来的新的props
pendingProps: any,
// 上一次渲染完成之后的props
memoizedProps: any,

// 该Fiber对应的组件产生的Update会存放在这个队列里面
updateQueue: UpdateQueue<any> | null,

// 上一次渲染的时候的state
memoizedState: any,

// 一个列表,存放这个Fiber依赖的context
firstContextDependency: ContextDependency<mixed> | null,

// 用来描述当前Fiber和他子树的`Bitfield`
// 共存的模式表示这个子树是否默认是异步渲染的
// Fiber被创建的时候他会继承父Fiber
// 其他的标识也可以在创建的时候被设置
// 但是在创建之后不应该再被修改,特别是他的子Fiber创建之前
mode: TypeOfMode,

// Effect
// 用来记录Side Effect
effectTag: SideEffectTag,

// 单链表用来快速查找下一个side effect
nextEffect: Fiber | null,

// 子树中第一个side effect
firstEffect: Fiber | null,
// 子树中最后一个side effect
lastEffect: Fiber | null,

// 代表任务在未来的哪个时间点应该被完成
// 不包括他的子树产生的任务
expirationTime: ExpirationTime,

// 快速确定子树中是否有不在等待的变化
childExpirationTime: ExpirationTime,

// 在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber
// 我们称他为`current <==> workInProgress`
// 在渲染完成之后他们会交换位置
alternate: Fiber | null,

// 下面是调试相关的,收集每个Fiber和子树渲染时间的

actualDuration?: number,

// If the Fiber is currently active in the "render" phase,
// This marks the time at which the work began.
// This field is only set when the enableProfilerTimer flag is enabled.
actualStartTime?: number,

// Duration of the most recent render time for this Fiber.
// This value is not updated when we bailout for memoization purposes.
// This field is only set when the enableProfilerTimer flag is enabled.
selfBaseDuration?: number,

// Sum of base times for all descedents of this Fiber.
// This value bubbles up during the "complete" phase.
// This field is only set when the enableProfilerTimer flag is enabled.
treeBaseDuration?: number,

// Conceptual aliases
// workInProgress : Fiber -> alternate The alternate used for reuse happens
// to be the same as work in progress.
// __DEV__ only
_debugID?: number,
_debugSource?: Source | null,
_debugOwner?: Fiber | null,
_debugIsCurrentlyTiming?: boolean,
|};

React会基于Fiber数据结构reconciliation。源代码基本都在react-reconciler中。官方文档也对reconciliation进行了说明

Fiber树的组织形式

我们围绕上一节的Demo来讲解。
最终会生成这种形式的Fiber树。

ClassComponent的state和props都会记录到Fiber上,这样即使是FunctionComponet,没有this,也可以用hooks。FiberRoot也就是上节创建的ReactRoot。FiberRoot的current属性指向RootFiber。
Fiber节点是用child、sibling、return串联起整个树。那么这整颗树是如何构建起来的。

ReactDom.render的fiber树的构建

我们接上篇文章的scheduleWork。进入React的第二层-内部组件层。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
// 找到FiberRoot. sheduleWork不仅仅是开始的时候调用。也有可能是setState。
// 所以这里的fiber不一定就是RootFiber。所以先找到RootFiber
const root = scheduleWorkToRoot(fiber, expirationTime);
if (root === null) {
return;
}

if (
!isWorking &&
nextRenderExpirationTime !== NoWork &&
expirationTime < nextRenderExpirationTime
) {
// This is an interruption. (Used for performance tracking.)
interruptedBy = fiber;
resetStack();
}
markPendingPriorityLevel(root, expirationTime);
if (
// If we're in the render phase, we don't need to schedule this root
// for an update, because we'll do it before we exit...
!isWorking ||
isCommitting ||
// ...unless this is a different root than the one we're rendering.
nextRoot !== root
) {
const rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime);
}
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
// 防止无限循环地嵌套更新
// Reset this back to zero so subsequent updates don't throw.
nestedUpdateCount = 0;
invariant(
false,
'Maximum update depth exceeded. This can happen when a ' +
'component repeatedly calls setState inside ' +
'componentWillUpdate or componentDidUpdate. React limits ' +
'the number of nested updates to prevent infinite loops.',
);
}
}


function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
recordScheduleUpdate();

if (__DEV__) {
if (fiber.tag === ClassComponent) {
const instance = fiber.stateNode;
warnAboutInvalidUpdates(instance);
}
}

// Update the source fiber's expiration time
// 也就是说,当前fiber的优先级是小于expirationTime的优先级的,现在要调高fiber的优先级
if (
fiber.expirationTime === NoWork ||
fiber.expirationTime > expirationTime
) {
fiber.expirationTime = expirationTime;
}
// 更新alternate的expirationTime,alternate是fiber的一个共享节点。
let alternate = fiber.alternate;
if (
alternate !== null &&
(alternate.expirationTime === NoWork ||
alternate.expirationTime > expirationTime)
) {
alternate.expirationTime = expirationTime;
}
// 向上遍历父节点,直到root节点,在遍历的过程中更新子节点的expirationTime。
// Walk the parent path to the root and update the child expiration time.
let node = fiber.return;
let root = null;
if (node === null && fiber.tag === HostRoot) {
// 找到RootFiber
root = fiber.stateNode;
} else {
// 递归找到RootFiber
while (node !== null) {
alternate = node.alternate;
// 更新childExpirationTime
// 如果父节点的所有子节点中优先级最高的更新时间仍小于expirationTime的话,则提高优先级
if (
node.childExpirationTime === NoWork ||
node.childExpirationTime > expirationTime
) {
node.childExpirationTime = expirationTime;
// alternate是相对于fiber的另一个对象,也要进行更新
if (
alternate !== null &&
(alternate.childExpirationTime === NoWork ||
alternate.childExpirationTime > expirationTime)
) {
alternate.childExpirationTime = expirationTime;
}
} else if (
//更新alternate childExpirationTime
alternate !== null &&
(alternate.childExpirationTime === NoWork ||
alternate.childExpirationTime > expirationTime)
) {
alternate.childExpirationTime = expirationTime;
}
//如果找到顶端rootFiber,结束循环
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
node = node.return;
}
}

if (root === null) {
if (__DEV__ && fiber.tag === ClassComponent) {
warnAboutUpdateOnUnmounted(fiber);
}
return null;
}
//
if (enableSchedulerTracing) {
const interactions = __interactionsRef.current;
if (interactions.size > 0) {
const pendingInteractionMap = root.pendingInteractionMap;
const pendingInteractions = pendingInteractionMap.get(expirationTime);
if (pendingInteractions != null) {
interactions.forEach(interaction => {
if (!pendingInteractions.has(interaction)) {
// Update the pending async work count for previously unscheduled interaction.
interaction.__count++;
}

pendingInteractions.add(interaction);
});
} else {
pendingInteractionMap.set(expirationTime, new Set(interactions));

// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interaction.__count++;
});
}

const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(
expirationTime,
root.interactionThreadID,
);
subscriber.onWorkScheduled(interactions, threadID);
}
}
}

return root;
}

scheduleWorkToRoot是找到根节点,并遍历更新子节点的expirationTime(这个属性是用来表示优先级的,异步调度会用到)。然后进入requestWork

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
// 将这个root加入到调度队列,这里主要考虑了有多个的Fiber树的情况。
addRootToSchedule(root, expirationTime);
if (isRendering) {
// 如果处于 render 阶段,函数执行结束,不需要再次调度。
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}

if (isBatchingUpdates) {
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// 这里如何是强制不批量更新,我们直接调度。
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, true);
}
return;
}

// TODO: Get rid of Sync and use current time?
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}

//将这个root加入到root调度队列。
function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
// Add the root to the schedule.
// Check if this root is already part of the schedule.
if (root.nextScheduledRoot === null) {
// This root is not already scheduled. Add it.
root.expirationTime = expirationTime;
if (lastScheduledRoot === null) {
firstScheduledRoot = lastScheduledRoot = root;
root.nextScheduledRoot = root;
} else {
lastScheduledRoot.nextScheduledRoot = root;
lastScheduledRoot = root;
lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
}
} else {
// This root is already scheduled, but its priority may have increased.
const remainingExpirationTime = root.expirationTime;
if (
remainingExpirationTime === NoWork ||
expirationTime < remainingExpirationTime
) {
// Update the priority.
root.expirationTime = expirationTime;
}
}
}

addRootToSchedule是考虑有多个fiber树的情况,也就是多个root,对不同的root实现调度。方法利用FiberRoot的nextScheduledRoot属性,以及firstScheduledRootlastScheduledRoot构成一个链表。同updateQueue相同。
下面performSyncWorkscheduleCallbackWithExpirationTime是开始进行同步和异步协调任务。异步任务协调涉及到react-scheduler。我们在下一节再说。我们用同步调度把reconciliation流程理清楚。进入performSyncWork

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
deadline = dl;

// Keep working on roots until there's no more work, or until we reach
// the deadline.
findHighestPriorityRoot();

if (deadline !== null) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;

if (enableUserTimingAPI) {
const didExpire = nextFlushedExpirationTime < currentRendererTime;
const timeout = expirationTimeToMs(nextFlushedExpirationTime);
stopRequestCallbackTimer(didExpire, timeout);
}
// nextFlushedExpirationTime是在findHighestPriorityRoot阶段读取出来的root.expirationTime。
while (
nextFlushedRoot !== null &&
nextFlushedExpirationTime !== NoWork &&
(minExpirationTime === NoWork ||
minExpirationTime >= nextFlushedExpirationTime) &&
(!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime)
) {
performWorkOnRoot(
nextFlushedRoot,
nextFlushedExpirationTime,
// 这个参数表示isExpired.如果过期则强制更新.
currentRendererTime >= nextFlushedExpirationTime,
);
findHighestPriorityRoot();
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
}
} else {
// 更新同步的任务.
while (
nextFlushedRoot !== null &&
nextFlushedExpirationTime !== NoWork &&
(minExpirationTime === NoWork ||
minExpirationTime >= nextFlushedExpirationTime)
) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
findHighestPriorityRoot();
}
}

// We're done flushing work. Either we ran out of time in this callback,
// or there's no more work left with sufficient priority.

// If we're inside a callback, set this to false since we just completed it.
if (deadline !== null) {
callbackExpirationTime = NoWork;
callbackID = null;
}
// If there's work left over, schedule a new callback.
if (nextFlushedExpirationTime !== NoWork) {
scheduleCallbackWithExpirationTime(
((nextFlushedRoot: any): FiberRoot),
nextFlushedExpirationTime,
);
}

// Clean-up.
deadline = null;
deadlineDidExpire = false;

finishRendering();
}

同步的deadline传入的为空。进入performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true)中。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
function performWorkOnRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
isExpired: boolean,
) {
invariant(
!isRendering,
'performWorkOnRoot was called recursively. This error is likely caused ' +
'by a bug in React. Please file an issue.',
);

isRendering = true;

// Check if this is async work or sync/expired work.
if (deadline === null || isExpired) {
// Flush work without yielding.
// TODO: Non-yieldy work does not necessarily imply expired work. A renderer
// may want to perform some work without yielding, but also without
// requiring the root to complete (by triggering placeholders).

let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
// If this root previously suspended, clear its existing timeout, since
// we're about to try rendering again.
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
// isYieldy判断异步任务是否可以中断.
const isYieldy = false;
renderRoot(root, isYieldy, isExpired);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
}
} else {
// Flush async work.
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
// If this root previously suspended, clear its existing timeout, since
// we're about to try rendering again.
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
const isYieldy = true;
renderRoot(root, isYieldy, isExpired);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
// We've completed the root. Check the deadline one more time
// before committing.
if (!shouldYield()) {
// Still time left. Commit the root.
completeRoot(root, finishedWork, expirationTime);
} else {
// There's no time left. Mark this root as complete. We'll come
// back and commit it later.
root.finishedWork = finishedWork;
}
}
}
}

isRendering = false;
}

deadline === null || isExpired这种情况表示是不能中断的。这2个参数也很好理解就是没有时间的异步任务或者是同步任务。
renderRoot表示协调算法中的render阶段。commitRoot表示协调算法中commit阶段。render阶段是可以打断的,异步的。也是无副作用的。
finishedWork如果不为空,则表示任务render阶段已经完成,进入commit阶段。

reconciliation算法

该算法分为2个阶段,render 和 commit。
在第一阶段,React利用setState或者React.render更新组件。产生updater,所以updater相当于React内部和UI之间的一个桥梁。具体的更新细节可以读(https://medium.com/react-in-depth/in-depth-explanation-of-state-and-props-update-in-react-51ab94563311)[这篇文章]。这里大致谈谈算法的架构。

render阶段

render阶段是由workLoop贯穿其中的。由renderRoot进入。react会忽略已经处理过的Fiber节点,也就是updateQueue !== null 的fiber。workLoop大致流程如下:

1
2
3
4
5
6
7
function workLoop(isYieldy) {
if (!isYieldy) {
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {...}
}

nextUnitOfWork代表中Fiber每个节点的引用。React在workLoop中遍历整个Fiber树。performUnitOfWork处理当前节点,返回下一个Fiber节点。

1
2
3
4
5
6
7
8
9
10
11
12
function performUnitOfWork(workInProgress) {
let next = beginWork(workInProgress);
if (next === null) {
next = completeUnitOfWork(workInProgress);
}
return next;
}

function beginWork(workInProgress) {
console.log('work performed for ' + workInProgress.name);
return workInProgress.child;
}

performUnitOfWork主要将当前节点利用beginWork来做一些事。beginWork是一个switch,利用不同Fiber.tag来处理不同的情况。其中包括更新props和state、调用一些前期生命周期函数、添加componentDidUpdate触发器等。这里先不做深入探讨。beginWork始终返回要在循环中处理的下一个节点的指针或是null。
如果beginWork返回的为空。则表示到达分支末尾。进入completeUnitOfWork,一旦该节点完成,则需要为同层的其它节点执行工作,并在完成后回溯到父节点。

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
function completeUnitOfWork(workInProgress) {
while (true) {
let returnFiber = workInProgress.return;
let siblingFiber = workInProgress.sibling;

nextUnitOfWork = completeWork(workInProgress);

if (siblingFiber !== null) {
// If there is a sibling, return it
// to perform work for this sibling
return siblingFiber;
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber,
// continue the loop to complete the parent.
workInProgress = returnFiber;
continue;
} else {
// We've reached the root.
return null;
}
}
}

function completeWork(workInProgress) {
console.log('work completed for ' + workInProgress.name);
return null;
}

你可以看到函数的核心就是一个大的 while 的循环。当 workInProgress 节点没有子节点时,React 会进入此函数。完成当前 Fiber 节点的工作后,它就会检查是否有同层节点。如果找的到,React 退出该函数并返回指向该同层节点的指针。它将被赋值给 nextUnitOfWork 变量,React将从这个节点开始执行分支的工作。我们需要着重理解的是,在当前节点上,React 只完成了前面的同层节点的工作。它尚未完成父节点的工作。只有在完成以子节点开始的所有分支后,才能完成父节点和回溯的工作。
整个的流程图:render-stage

commit阶段

commit阶段是由commitRoot进入。在这个阶段Ract更新DOM之前或之后的生命周期方法。

  • 在标记为Snapshot副作用的节点上调用getSnapshotBeforeUpdate生命周期
  • 在标记为Deletion副作用的节点上调用componentWillUnmount生命周期
  • 执行所有DOM插入、更新、删除操作
  • finishedWork 树设置为current
  • 在标记为Placement副作用的节点上调用componentDidMount生命周期
  • 在标记为Update副作用的节点上调用componentDidUpdate生命周期
1
2
3
4
5
6
function commitRoot(root, finishedWork) {
commitBeforeMutationLifecycles()
commitAllHostEffects();
root.current = finishedWork;
commitAllLifeCycles();
}

commitBeforeMutationLifecycles是更新前的生命周期方法,并且检查副作用树上节点是否有Snapshot副作用的代码。

1
2
3
4
5
6
7
8
9
10
function commitBeforeMutationLifecycles() {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & Snapshot) {
const current = nextEffect.alternate;
commitBeforeMutationLifeCycles(current, nextEffect);
}
nextEffect = nextEffect.nextEffect;
}
}

commitAllHostEffects 是React执行DOM更新的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function commitAllHostEffects() {
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
...
}
case PlacementAndUpdate: {
commitPlacement(nextEffect);
commitWork(current, nextEffect);
...
}
case Update: {
commitWork(current, nextEffect);
...
}
case Deletion: {
commitDeletion(nextEffect);
...
}
}
}

commitAllLifecycles是React调用所有剩余生命周期方法的函数。在React的当前实现中,唯一会调用的变更方法就是 componentDidUpdate。