-
Notifications
You must be signed in to change notification settings - Fork 45.5k
/
ReactFiberCompleteWork.js
309 lines (289 loc) · 11.5 KB
/
ReactFiberCompleteWork.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
/**
* 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 ReactFiberCompleteWork
* @flow
*/
'use strict';
import type { ReactCoroutine } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
import type { ReifiedYield } from 'ReactReifiedYield';
var { reconcileChildFibers } = require('ReactChildFiber');
var {
isContextProvider,
popContextProvider,
} = require('ReactFiberContext');
var ReactTypeOfWork = require('ReactTypeOfWork');
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
var {
IndeterminateComponent,
FunctionalComponent,
ClassComponent,
HostContainer,
HostComponent,
HostText,
CoroutineComponent,
CoroutineHandlerPhase,
YieldComponent,
Fragment,
Portal,
} = ReactTypeOfWork;
var {
Update,
Callback,
} = ReactTypeOfSideEffect;
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
const createInstance = config.createInstance;
const appendInitialChild = config.appendInitialChild;
const finalizeInitialChildren = config.finalizeInitialChildren;
const createTextInstance = config.createTextInstance;
const prepareUpdate = config.prepareUpdate;
function markUpdate(workInProgress : Fiber) {
// Tag the fiber with an update effect. This turns a Placement into
// an UpdateAndPlacement.
workInProgress.effectTag |= Update;
}
function markCallback(workInProgress : Fiber) {
// Tag the fiber with a callback effect.
workInProgress.effectTag |= Callback;
}
function transferOutput(child : ?Fiber, returnFiber : Fiber) {
// If we have a single result, we just pass that through as the output to
// avoid unnecessary traversal. When we have multiple output, we just pass
// the linked list of fibers that has the individual output values.
returnFiber.output = (child && !child.sibling) ? child.output : child;
returnFiber.memoizedProps = returnFiber.pendingProps;
}
function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
if (!output) {
// Ignore nulls etc.
} else if (output.tag !== undefined) { // TODO: Fix this fragile duck test.
// Detect if this is a fiber, if so it is a fragment result.
// $FlowFixMe: Refinement issue.
var item = (output : Fiber);
do {
recursivelyFillYields(yields, item.output);
item = item.sibling;
} while (item);
} else {
// $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield
yields.push(output);
}
}
function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
if (!coroutine) {
throw new Error('Should be resolved by now');
}
// First step of the coroutine has completed. Now we need to do the second.
// TODO: It would be nice to have a multi stage coroutine represented by a
// single component, or at least tail call optimize nested ones. Currently
// that requires additional fields that we don't want to add to the fiber.
// So this requires nested handlers.
// Note: This doesn't mutate the alternate node. I don't think it needs to
// since this stage is reset for every pass.
workInProgress.tag = CoroutineHandlerPhase;
// Build up the yields.
// TODO: Compare this to a generator or opaque helpers like Children.
var yields : Array<ReifiedYield> = [];
var child = workInProgress.child;
while (child) {
recursivelyFillYields(yields, child.output);
child = child.sibling;
}
var fn = coroutine.handler;
var props = coroutine.props;
var nextChildren = fn(props, yields);
var currentFirstChild = current ? current.stateNode : null;
// Inherit the priority of the returnFiber.
const priority = workInProgress.pendingWorkPriority;
workInProgress.stateNode = reconcileChildFibers(
workInProgress,
currentFirstChild,
nextChildren,
priority
);
return workInProgress.stateNode;
}
function appendAllChildren(parent : I, workInProgress : Fiber) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
let node = workInProgress.child;
while (node) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (node.tag === Portal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child) {
// TODO: Coroutines need to visit the stateNode.
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (!node.sibling) {
if (!node.return || node.return === workInProgress) {
return;
}
node = node.return;
}
node = node.sibling;
}
}
function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
switch (workInProgress.tag) {
case FunctionalComponent:
transferOutput(workInProgress.child, workInProgress);
return null;
case ClassComponent:
transferOutput(workInProgress.child, workInProgress);
// We are leaving this subtree, so pop context if any.
if (isContextProvider(workInProgress)) {
popContextProvider();
}
// Don't use the state queue to compute the memoized state. We already
// merged it and assigned it to the instance. Transfer it from there.
// Also need to transfer the props, because pendingProps will be null
// in the case of an update
const { state, props } = workInProgress.stateNode;
const updateQueue = workInProgress.updateQueue;
workInProgress.memoizedState = state;
workInProgress.memoizedProps = props;
if (current) {
if (current.memoizedProps !== workInProgress.memoizedProps ||
current.memoizedState !== workInProgress.memoizedState ||
updateQueue && updateQueue.isForced) {
markUpdate(workInProgress);
}
} else {
markUpdate(workInProgress);
}
if (updateQueue && updateQueue.hasCallback) {
// Transfer update queue to callbackList field so callbacks can be
// called during commit phase.
workInProgress.callbackList = updateQueue;
markCallback(workInProgress);
}
return null;
case HostContainer: {
transferOutput(workInProgress.child, workInProgress);
popContextProvider();
const fiberRoot = (workInProgress.stateNode : FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
// TODO: Only mark this as an update if we have any pending callbacks
// on it.
markUpdate(workInProgress);
return null;
}
case HostComponent:
let newProps = workInProgress.pendingProps;
if (current && workInProgress.stateNode != null) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
const oldProps = current.memoizedProps;
// If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
if (!newProps) {
newProps = workInProgress.memoizedProps || oldProps;
}
const instance : I = workInProgress.stateNode;
if (prepareUpdate(instance, oldProps, newProps)) {
// This returns true if there was something to update.
markUpdate(workInProgress);
}
// TODO: Is this actually ever going to change? Why set it every time?
workInProgress.output = instance;
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
throw new Error('We must have new props for new mounts.');
} else {
// This can happen when we abort work.
return null;
}
}
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
// Finally, finalizeInitialChildren here in completeWork.
const instance = createInstance(workInProgress.type, newProps, workInProgress);
appendAllChildren(instance, workInProgress);
finalizeInitialChildren(instance, workInProgress.type, newProps);
// TODO: This seems like unnecessary duplication.
workInProgress.stateNode = instance;
workInProgress.output = instance;
if (workInProgress.ref) {
// If there is a ref on a host node we need to schedule a callback
markUpdate(workInProgress);
}
}
workInProgress.memoizedProps = newProps;
return null;
case HostText:
let newText = workInProgress.pendingProps;
if (current && workInProgress.stateNode != null) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
markUpdate(workInProgress);
} else {
if (typeof newText !== 'string') {
if (workInProgress.stateNode === null) {
throw new Error('We must have new props for new mounts.');
} else {
// This can happen when we abort work.
return null;
}
}
const textInstance = createTextInstance(newText, workInProgress);
// TODO: This seems like unnecessary duplication.
workInProgress.stateNode = textInstance;
workInProgress.output = textInstance;
}
workInProgress.memoizedProps = newText;
return null;
case CoroutineComponent:
return moveCoroutineToHandlerPhase(current, workInProgress);
case CoroutineHandlerPhase:
transferOutput(workInProgress.stateNode, workInProgress);
// Reset the tag to now be a first phase coroutine.
workInProgress.tag = CoroutineComponent;
return null;
case YieldComponent:
// Does nothing.
return null;
case Fragment:
transferOutput(workInProgress.child, workInProgress);
return null;
case Portal:
// TODO: Only mark this as an update if we have any pending callbacks.
markUpdate(workInProgress);
workInProgress.output = null;
workInProgress.memoizedProps = workInProgress.pendingProps;
return null;
// Error cases
case IndeterminateComponent:
throw new Error('An indeterminate component should have become determinate before completing.');
default:
throw new Error('Unknown unit of work tag');
}
}
return {
completeWork,
};
};