-
Notifications
You must be signed in to change notification settings - Fork 47.6k
/
ReactFiberCommitWork.js
332 lines (312 loc) · 10.2 KB
/
ReactFiberCommitWork.js
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactFiberCommitWork
* @flow
*/
'use strict';
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
var ReactTypeOfWork = require('ReactTypeOfWork');
var {
ClassComponent,
HostContainer,
HostComponent,
HostText,
} = ReactTypeOfWork;
var { callCallbacks } = require('ReactFiberUpdateQueue');
var {
Placement,
PlacementAndUpdate,
} = require('ReactTypeOfSideEffect');
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
const updateContainer = config.updateContainer;
const commitUpdate = config.commitUpdate;
const commitTextUpdate = config.commitTextUpdate;
const appendChild = config.appendChild;
const insertBefore = config.insertBefore;
const removeChild = config.removeChild;
function detachRef(current : Fiber) {
const ref = current.ref;
if (ref) {
ref(null);
}
}
function detachRefIfNeeded(current : ?Fiber, finishedWork : Fiber) {
if (current) {
const currentRef = current.ref;
if (currentRef && currentRef !== finishedWork.ref) {
currentRef(null);
}
}
}
function attachRef(current : ?Fiber, finishedWork : Fiber, instance : any) {
const ref = finishedWork.ref;
if (ref && (!current || current.ref !== ref)) {
ref(instance);
}
}
function getHostParent(fiber : Fiber) : ?I {
let parent = fiber.return;
while (parent) {
switch (parent.tag) {
case HostComponent:
return parent.stateNode;
case HostContainer:
// TODO: Currently we use the updateContainer feature to update these,
// but we should be able to handle this case too.
return null;
}
parent = parent.return;
}
return null;
}
function getHostSibling(fiber : Fiber) : ?I {
// We're going to search forward into the tree until we find a sibling host
// node. Unfortunately, if multiple insertions are done in a row we have to
// search past them. This leads to exponential search for the next sibling.
// TODO: Find a more efficient way to do this.
let node : Fiber = fiber;
siblings: while (true) {
// If we didn't find anything, let's try the next sibling.
while (!node.sibling) {
if (!node.return || node.return.tag === HostComponent) {
// If we pop out of the root or hit the parent the fiber we are the
// last sibling.
return null;
}
node = node.return;
}
node = node.sibling;
while (node.tag !== HostComponent && node.tag !== HostText) {
// If it is not host node and, we might have a host node inside it.
// Try to search down until we find one.
// TODO: For coroutines, this will have to search the stateNode.
if (node.effectTag === Placement ||
node.effectTag === PlacementAndUpdate) {
// If we don't have a child, try the siblings instead.
continue siblings;
}
if (!node.child) {
continue siblings;
} else {
node = node.child;
}
}
// Check if this host node is stable or about to be placed.
if (node.effectTag !== Placement &&
node.effectTag !== PlacementAndUpdate) {
// Found it!
return node.stateNode;
}
}
}
function commitInsertion(finishedWork : Fiber) : void {
// Recursively insert all host nodes into the parent.
const parent = getHostParent(finishedWork);
if (!parent) {
return;
}
const before = getHostSibling(finishedWork);
// We only have the top Fiber that was inserted but we need recurse down its
// children to find all the terminal nodes.
let node : Fiber = finishedWork;
while (true) {
if (node.tag === HostComponent || node.tag === HostText) {
if (before) {
insertBefore(parent, node.stateNode, before);
} else {
appendChild(parent, node.stateNode);
}
} else if (node.child) {
// TODO: Coroutines need to visit the stateNode.
node = node.child;
continue;
}
if (node === finishedWork) {
return;
}
while (!node.sibling) {
if (!node.return || node.return === finishedWork) {
return;
}
node = node.return;
}
node = node.sibling;
}
}
function commitNestedUnmounts(root : Fiber) {
// While we're inside a removed host node we don't want to call
// removeChild on the inner nodes because they're removed by the top
// call anyway. We also want to call componentWillUnmount on all
// composites before this host node is removed from the tree. Therefore
// we do an inner loop while we're still inside the host node.
let node : Fiber = root;
while (true) {
commitUnmount(node);
if (node.child) {
// TODO: Coroutines need to visit the stateNode.
node = node.child;
continue;
}
if (node === root) {
return;
}
while (!node.sibling) {
if (!node.return || node.return === root) {
return;
}
node = node.return;
}
node = node.sibling;
}
}
function commitDeletion(current : Fiber) : void {
// Recursively delete all host nodes from the parent.
// TODO: Error handling.
const parent = getHostParent(current);
// We only have the top Fiber that was inserted but we need recurse down its
// children to find all the terminal nodes.
// TODO: Call componentWillUnmount on all classes as needed. Recurse down
// removed HostComponents but don't call removeChild on already removed
// children.
let node : Fiber = current;
while (true) {
if (node.tag === HostComponent || node.tag === HostText) {
commitNestedUnmounts(node);
// After all the children have unmounted, it is now safe to remove the
// node from the tree.
if (parent) {
removeChild(parent, node.stateNode);
}
} else {
commitUnmount(node);
if (node.child) {
// TODO: Coroutines need to visit the stateNode.
node = node.child;
continue;
}
}
if (node === current) {
return;
}
while (!node.sibling) {
if (!node.return || node.return === current) {
return;
}
node = node.return;
}
node = node.sibling;
}
}
function commitUnmount(current : Fiber) : void {
switch (current.tag) {
case ClassComponent: {
detachRef(current);
const instance = current.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
instance.componentWillUnmount();
}
return;
}
case HostComponent: {
detachRef(current);
return;
}
}
}
function commitWork(current : ?Fiber, finishedWork : Fiber) : void {
switch (finishedWork.tag) {
case ClassComponent: {
detachRefIfNeeded(current, finishedWork);
return;
}
case HostContainer: {
// TODO: Attach children to root container.
const children = finishedWork.output;
const root : FiberRoot = finishedWork.stateNode;
const containerInfo : C = root.containerInfo;
updateContainer(containerInfo, children);
return;
}
case HostComponent: {
const instance : I = finishedWork.stateNode;
if (instance != null && current) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
commitUpdate(instance, oldProps, newProps);
}
detachRefIfNeeded(current, finishedWork);
return;
}
case HostText: {
if (finishedWork.stateNode == null || !current) {
throw new Error('This should only be done during updates.');
}
const textInstance : TI = finishedWork.stateNode;
const newText : string = finishedWork.memoizedProps;
const oldText : string = current.memoizedProps;
commitTextUpdate(textInstance, oldText, newText);
return;
}
default:
throw new Error('This unit of work tag should not have side-effects.');
}
}
function commitLifeCycles(current : ?Fiber, finishedWork : Fiber) : void {
switch (finishedWork.tag) {
case ClassComponent: {
const instance = finishedWork.stateNode;
if (!current) {
if (typeof instance.componentDidMount === 'function') {
instance.componentDidMount();
}
} else {
if (typeof instance.componentDidUpdate === 'function') {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
instance.componentDidUpdate(prevProps, prevState);
}
}
// Clear updates from current fiber. This must go before the callbacks
// are reset, in case an update is triggered from inside a callback. Is
// this safe? Relies on the assumption that work is only committed if
// the update queue is empty.
if (finishedWork.alternate) {
finishedWork.alternate.updateQueue = null;
}
if (finishedWork.callbackList) {
const { callbackList } = finishedWork;
finishedWork.callbackList = null;
callCallbacks(callbackList, instance);
}
attachRef(current, finishedWork, instance);
return;
}
case HostComponent: {
const instance : I = finishedWork.stateNode;
attachRef(current, finishedWork, instance);
return;
}
case HostText: {
// We have no life-cycles associated with text.
return;
}
default:
throw new Error('This unit of work tag should not have side-effects.');
}
}
return {
commitInsertion,
commitDeletion,
commitWork,
commitLifeCycles,
};
};