Split out container methods in host config (#9983)
This makes it so you don't need to pattern-match manually to build a renderer where the container and instance types are not the same. Prerequisite to #9835.
Ben Alpert committed
Jun 16, 2017 at 15:00 UTC
5029c3d6bc8c9c7587a82ee02402d1b071eea659
8 files changed
+271
-183
src/renderers/art/ReactARTFiberEntry.js
+17
@@ -394,7 +394,13 @@ const ARTRenderer = ReactFiberReconciler({
394
if (child.parentNode === parentInstance) {
395
child.eject();
396
}
397
+ child.inject(parentInstance);
398
+ },
399
400
+ appendChildToContainer(parentInstance, child) {
401
+ if (child.parentNode === parentInstance) {
402
+ child.eject();
403
+ }
404
child.inject(parentInstance);
405
},
406
@@ -471,7 +477,14 @@ const ARTRenderer = ReactFiberReconciler({
477
child !== beforeChild,
478
'ReactART: Can not insert node before itself',
479
);
480
+ child.injectBefore(beforeChild);
481
+ },
482
483
+ insertInContainerBefore(parentInstance, child, beforeChild) {
484
+ invariant(
485
+ child !== beforeChild,
486
+ 'ReactART: Can not insert node before itself',
487
+ );
488
child.injectBefore(beforeChild);
489
},
490
@@ -485,7 +498,11 @@ const ARTRenderer = ReactFiberReconciler({
498
499
removeChild(parentInstance, child) {
500
destroyEventListeners(child);
501
+ child.eject();
502
+ },
503
504
+ removeChildFromContainer(parentInstance, child) {
505
+ destroyEventListeners(child);
506
child.eject();
507
},
508
src/renderers/dom/fiber/ReactDOMFiberEntry.js
+22
-6
@@ -346,28 +346,44 @@ var DOMRenderer = ReactFiberReconciler({
346
textInstance.nodeValue = newText;
347
},
348
349
- appendChild(
350
- parentInstance: Instance | Container,
349
+ appendChild(parentInstance: Instance, child: Instance | TextInstance): void {
350
+ parentInstance.appendChild(child);
351
+ },
352
+
353
+ appendChildToContainer(
354
+ container: Container,
355
child: Instance | TextInstance,
356
): void {
353
- parentInstance.appendChild(child);
357
+ container.appendChild(child);
358
},
359
360
insertBefore(
357
- parentInstance: Instance | Container,
361
+ parentInstance: Instance,
362
child: Instance | TextInstance,
363
beforeChild: Instance | TextInstance,
364
): void {
365
parentInstance.insertBefore(child, beforeChild);
366
},
367
364
- removeChild(
365
- parentInstance: Instance | Container,
368
+ insertInContainerBefore(
369
+ container: Container,
370
child: Instance | TextInstance,
371
+ beforeChild: Instance | TextInstance,
372
): void {
373
+ container.insertBefore(child, beforeChild);
374
+ },
375
+
376
+ removeChild(parentInstance: Instance, child: Instance | TextInstance): void {
377
parentInstance.removeChild(child);
378
},
379
380
+ removeChildFromContainer(
381
+ container: Container,
382
+ child: Instance | TextInstance,
383
+ ): void {
384
+ container.removeChild(child);
385
+ },
386
+
387
canHydrateInstance(
388
instance: Instance | TextInstance,
389
type: string,
src/renderers/native/ReactNativeFiberRenderer.js
+77
-74
@@ -55,50 +55,48 @@ function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
55
}
56
57
const NativeRenderer = ReactFiberReconciler({
58
- appendChild(
59
- parentInstance: Instance | Container,
60
- child: Instance | TextInstance,
61
- ): void {
58
+ appendChild(parentInstance: Instance, child: Instance | TextInstance): void {
59
const childTag = typeof child === 'number' ? child : child._nativeTag;
60
+ const children = parentInstance._children;
61
+ const index = children.indexOf(child);
62
+
63
+ if (index >= 0) {
64
+ children.splice(index, 1);
65
+ children.push(child);
66
64
- if (typeof parentInstance === 'number') {
65
- // Root container
66
- UIManager.setChildren(
67
- parentInstance, // containerTag
68
- [childTag], // reactTags
67
+ UIManager.manageChildren(
68
+ parentInstance._nativeTag, // containerTag
69
+ [index], // moveFromIndices
70
+ [children.length - 1], // moveToIndices
71
+ [], // addChildReactTags
72
+ [], // addAtIndices
73
+ [], // removeAtIndices
74
);
75
} else {
71
- const children = parentInstance._children;
72
-
73
- const index = children.indexOf(child);
74
-
75
- if (index >= 0) {
76
- children.splice(index, 1);
77
- children.push(child);
78
-
79
- UIManager.manageChildren(
80
- parentInstance._nativeTag, // containerTag
81
- [index], // moveFromIndices
82
- [children.length - 1], // moveToIndices
83
- [], // addChildReactTags
84
- [], // addAtIndices
85
- [], // removeAtIndices
86
- );
87
- } else {
88
- children.push(child);
89
-
90
- UIManager.manageChildren(
91
- parentInstance._nativeTag, // containerTag
92
- [], // moveFromIndices
93
- [], // moveToIndices
94
- [childTag], // addChildReactTags
95
- [children.length - 1], // addAtIndices
96
- [], // removeAtIndices
97
- );
98
- }
76
+ children.push(child);
77
+
78
+ UIManager.manageChildren(
79
+ parentInstance._nativeTag, // containerTag
80
+ [], // moveFromIndices
81
+ [], // moveToIndices
82
+ [childTag], // addChildReactTags
83
+ [children.length - 1], // addAtIndices
84
+ [], // removeAtIndices
85
+ );
86
}
87
},
88
89
+ appendChildToContainer(
90
+ parentInstance: Container,
91
+ child: Instance | TextInstance,
92
+ ): void {
93
+ const childTag = typeof child === 'number' ? child : child._nativeTag;
94
+ UIManager.setChildren(
95
+ parentInstance, // containerTag
96
+ [childTag], // reactTags
97
+ );
98
+ },
99
+
100
appendInitialChild(
101
parentInstance: Instance,
102
child: Instance | TextInstance,
@@ -253,21 +251,11 @@ const NativeRenderer = ReactFiberReconciler({
251
},
252
253
insertBefore(
256
- parentInstance: Instance | Container,
254
+ parentInstance: Instance,
255
child: Instance | TextInstance,
256
beforeChild: Instance | TextInstance,
257
): void {
260
- // TODO (bvaughn): Remove this check when...
261
- // We create a wrapper object for the container in ReactNative render()
262
- // Or we refactor to remove wrapper objects entirely.
263
- // For more info on pros/cons see PR #8560 description.
264
- invariant(
265
- typeof parentInstance !== 'number',
266
- 'Container does not support insertBefore operation',
267
- );
268
-
258
const children = (parentInstance: any)._children;
270
-
259
const index = children.indexOf(child);
260
261
// Move existing child or add new child?
@@ -301,6 +289,21 @@ const NativeRenderer = ReactFiberReconciler({
289
}
290
},
291
292
+ insertInContainerBefore(
293
+ parentInstance: Container,
294
+ child: Instance | TextInstance,
295
+ beforeChild: Instance | TextInstance,
296
+ ): void {
297
+ // TODO (bvaughn): Remove this check when...
298
+ // We create a wrapper object for the container in ReactNative render()
299
+ // Or we refactor to remove wrapper objects entirely.
300
+ // For more info on pros/cons see PR #8560 description.
301
+ invariant(
302
+ typeof parentInstance !== 'number',
303
+ 'Container does not support insertBefore operation',
304
+ );
305
+ },
306
+
307
prepareForCommit(): void {
308
// Noop
309
},
@@ -316,36 +319,36 @@ const NativeRenderer = ReactFiberReconciler({
319
return emptyObject;
320
},
321
319
- removeChild(
320
- parentInstance: Instance | Container,
321
- child: Instance | TextInstance,
322
- ): void {
322
+ removeChild(parentInstance: Instance, child: Instance | TextInstance): void {
323
recursivelyUncacheFiberNode(child);
324
+ const children = parentInstance._children;
325
+ const index = children.indexOf(child);
326
325
- if (typeof parentInstance === 'number') {
326
- UIManager.manageChildren(
327
- parentInstance, // containerID
328
- [], // moveFromIndices
329
- [], // moveToIndices
330
- [], // addChildReactTags
331
- [], // addAtIndices
332
- [0], // removeAtIndices
333
- );
334
- } else {
335
- const children = parentInstance._children;
336
- const index = children.indexOf(child);
327
+ children.splice(index, 1);
328
338
- children.splice(index, 1);
329
+ UIManager.manageChildren(
330
+ parentInstance._nativeTag, // containerID
331
+ [], // moveFromIndices
332
+ [], // moveToIndices
333
+ [], // addChildReactTags
334
+ [], // addAtIndices
335
+ [index], // removeAtIndices
336
+ );
337
+ },
338
340
- UIManager.manageChildren(
341
- parentInstance._nativeTag, // containerID
342
- [], // moveFromIndices
343
- [], // moveToIndices
344
- [], // addChildReactTags
345
- [], // addAtIndices
346
- [index], // removeAtIndices
347
- );
348
- }
339
+ removeChildFromContainer(
340
+ parentInstance: Container,
341
+ child: Instance | TextInstance,
342
+ ): void {
343
+ recursivelyUncacheFiberNode(child);
344
+ UIManager.manageChildren(
345
+ parentInstance, // containerID
346
+ [], // moveFromIndices
347
+ [], // moveToIndices
348
+ [], // addChildReactTags
349
+ [], // addAtIndices
350
+ [0], // removeAtIndices
351
+ );
352
},
353
354
resetAfterCommit(): void {
src/renderers/noop/ReactNoopEntry.js
+44
-37
@@ -47,6 +47,44 @@ var instanceCounter = 0;
47
48
var failInBeginPhase = false;
49
50
+function appendChild(
51
+ parentInstance: Instance | Container,
52
+ child: Instance | TextInstance,
53
+): void {
54
+ const index = parentInstance.children.indexOf(child);
55
+ if (index !== -1) {
56
+ parentInstance.children.splice(index, 1);
57
+ }
58
+ parentInstance.children.push(child);
59
+}
60
+
61
+function insertBefore(
62
+ parentInstance: Instance | Container,
63
+ child: Instance | TextInstance,
64
+ beforeChild: Instance | TextInstance,
65
+): void {
66
+ const index = parentInstance.children.indexOf(child);
67
+ if (index !== -1) {
68
+ parentInstance.children.splice(index, 1);
69
+ }
70
+ const beforeIndex = parentInstance.children.indexOf(beforeChild);
71
+ if (beforeIndex === -1) {
72
+ throw new Error('This child does not exist.');
73
+ }
74
+ parentInstance.children.splice(beforeIndex, 0, child);
75
+}
76
+
77
+function removeChild(
78
+ parentInstance: Instance | Container,
79
+ child: Instance | TextInstance,
80
+): void {
81
+ const index = parentInstance.children.indexOf(child);
82
+ if (index === -1) {
83
+ throw new Error('This child does not exist.');
84
+ }
85
+ parentInstance.children.splice(index, 1);
86
+}
87
+
88
var NoopRenderer = ReactFiberReconciler({
89
getRootHostContext() {
90
if (failInBeginPhase) {
@@ -145,43 +183,12 @@ var NoopRenderer = ReactFiberReconciler({
183
textInstance.text = newText;
184
},
185
148
- appendChild(
149
- parentInstance: Instance | Container,
150
- child: Instance | TextInstance,
151
- ): void {
152
- const index = parentInstance.children.indexOf(child);
153
- if (index !== -1) {
154
- parentInstance.children.splice(index, 1);
155
- }
156
- parentInstance.children.push(child);
157
- },
158
-
159
- insertBefore(
160
- parentInstance: Instance | Container,
161
- child: Instance | TextInstance,
162
- beforeChild: Instance | TextInstance,
163
- ): void {
164
- const index = parentInstance.children.indexOf(child);
165
- if (index !== -1) {
166
- parentInstance.children.splice(index, 1);
167
- }
168
- const beforeIndex = parentInstance.children.indexOf(beforeChild);
169
- if (beforeIndex === -1) {
170
- throw new Error('This child does not exist.');
171
- }
172
- parentInstance.children.splice(beforeIndex, 0, child);
173
- },
174
-
175
- removeChild(
176
- parentInstance: Instance | Container,
177
- child: Instance | TextInstance,
178
- ): void {
179
- const index = parentInstance.children.indexOf(child);
180
- if (index === -1) {
181
- throw new Error('This child does not exist.');
182
- }
183
- parentInstance.children.splice(index, 1);
184
- },
186
+ appendChild: appendChild,
187
+ appendChildToContainer: appendChild,
188
+ insertBefore: insertBefore,
189
+ insertInContainerBefore: insertBefore,
190
+ removeChild: removeChild,
191
+ removeChildFromContainer: removeChild,
192
193
scheduleAnimationCallback(callback) {
194
if (scheduledAnimationCallback) {
src/renderers/shared/fiber/ReactFiberCommitWork.js
+61
-30
@@ -51,8 +51,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
51
resetTextContent,
52
commitTextUpdate,
53
appendChild,
54
+ appendChildToContainer,
55
insertBefore,
56
+ insertInContainerBefore,
57
removeChild,
58
+ removeChildFromContainer,
59
getPublicInstance,
60
} = config;
61
@@ -104,26 +107,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
107
}
108
}
109
107
- function getHostParent(fiber: Fiber): I | C {
108
- let parent = fiber.return;
109
- while (parent !== null) {
110
- switch (parent.tag) {
111
- case HostComponent:
112
- return parent.stateNode;
113
- case HostRoot:
114
- return parent.stateNode.containerInfo;
115
- case HostPortal:
116
- return parent.stateNode.containerInfo;
117
- }
118
- parent = parent.return;
119
- }
120
- invariant(
121
- false,
122
- 'Expected to find a host parent. This error is likely caused by a bug ' +
123
- 'in React. Please file an issue.',
124
- );
125
- }
126
-
110
function getHostParentFiber(fiber: Fiber): Fiber {
111
let parent = fiber.return;
112
while (parent !== null) {
@@ -193,15 +176,19 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
176
// Recursively insert all host nodes into the parent.
177
const parentFiber = getHostParentFiber(finishedWork);
178
let parent;
179
+ let isContainer;
180
switch (parentFiber.tag) {
181
case HostComponent:
182
parent = parentFiber.stateNode;
183
+ isContainer = false;
184
break;
185
case HostRoot:
186
parent = parentFiber.stateNode.containerInfo;
187
+ isContainer = true;
188
break;
189
case HostPortal:
190
parent = parentFiber.stateNode.containerInfo;
191
+ isContainer = true;
192
break;
193
default:
194
invariant(
@@ -224,9 +211,17 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
211
while (true) {
212
if (node.tag === HostComponent || node.tag === HostText) {
213
if (before) {
227
- insertBefore(parent, node.stateNode, before);
214
+ if (isContainer) {
215
+ insertInContainerBefore(parent, node.stateNode, before);
216
+ } else {
217
+ insertBefore(parent, node.stateNode, before);
218
+ }
219
} else {
229
- appendChild(parent, node.stateNode);
220
+ if (isContainer) {
221
+ appendChildToContainer(parent, node.stateNode);
222
+ } else {
223
+ appendChild(parent, node.stateNode);
224
+ }
225
}
226
} else if (node.tag === HostPortal) {
227
// If the insertion itself is a portal, then we don't want to traverse
@@ -281,21 +276,59 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
276
}
277
}
278
284
- function unmountHostComponents(parent, current): void {
279
+ function unmountHostComponents(current): void {
280
// We only have the top Fiber that was inserted but we need recurse down its
281
// children to find all the terminal nodes.
282
let node: Fiber = current;
283
+
284
+ // Each iteration, currentParent is populated with node's host parent if not
285
+ // currentParentIsValid.
286
+ let currentParentIsValid = false;
287
+ let currentParent;
288
+ let currentParentIsContainer;
289
+
290
while (true) {
291
+ if (!currentParentIsValid) {
292
+ let parent = node.return;
293
+ findParent: while (true) {
294
+ invariant(
295
+ parent !== null,
296
+ 'Expected to find a host parent. This error is likely caused by ' +
297
+ 'a bug in React. Please file an issue.',
298
+ );
299
+ switch (parent.tag) {
300
+ case HostComponent:
301
+ currentParent = parent.stateNode;
302
+ currentParentIsContainer = false;
303
+ break findParent;
304
+ case HostRoot:
305
+ currentParent = parent.stateNode.containerInfo;
306
+ currentParentIsContainer = true;
307
+ break findParent;
308
+ case HostPortal:
309
+ currentParent = parent.stateNode.containerInfo;
310
+ currentParentIsContainer = true;
311
+ break findParent;
312
+ }
313
+ parent = parent.return;
314
+ }
315
+ currentParentIsValid = true;
316
+ }
317
+
318
if (node.tag === HostComponent || node.tag === HostText) {
319
commitNestedUnmounts(node);
320
// After all the children have unmounted, it is now safe to remove the
321
// node from the tree.
293
- removeChild(parent, node.stateNode);
322
+ if (currentParentIsContainer) {
323
+ removeChildFromContainer((currentParent: any), node.stateNode);
324
+ } else {
325
+ removeChild((currentParent: any), node.stateNode);
326
+ }
327
// Don't visit children because we already visited them.
328
} else if (node.tag === HostPortal) {
329
// When we go into a portal, it becomes the parent to remove from.
330
// We will reassign it back when we pop the portal on the way up.
298
- parent = node.stateNode.containerInfo;
331
+ currentParent = node.stateNode.containerInfo;
332
// Visit children because portals might contain host components.
333
if (node.child !== null) {
334
node.child.return = node;
@@ -322,7 +355,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
355
if (node.tag === HostPortal) {
356
// When we go out of the portal, we need to restore the parent.
357
// Since we don't keep a stack of them, we will search for it.
325
- parent = getHostParent(node);
358
+ currentParentIsValid = false;
359
}
360
}
361
node.sibling.return = node.return;
@@ -332,9 +365,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
365
366
function commitDeletion(current: Fiber): void {
367
// Recursively delete all host nodes from the parent.
335
- const parent = getHostParent(current);
368
// Detach refs and call componentWillUnmount() on the whole subtree.
337
- unmountHostComponents(parent, current);
369
+ unmountHostComponents(current);
370
371
// Cut off the return pointers to disconnect it from the tree. Ideally, we
372
// should clear the child pointer of the parent alternate to let this
@@ -378,8 +410,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
410
// TODO: this is recursive.
411
// We are also not using this parent because
412
// the portal will get pushed immediately.
381
- const parent = getHostParent(current);
382
- unmountHostComponents(parent, current);
413
+ unmountHostComponents(current);
414
return;
415
}
416
}
src/renderers/shared/fiber/ReactFiberReconciler.js
+11
-4
@@ -103,9 +103,16 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
103
): TI,
104
commitTextUpdate(textInstance: TI, oldText: string, newText: string): void,
105
106
- appendChild(parentInstance: I | C, child: I | TI): void,
107
- insertBefore(parentInstance: I | C, child: I | TI, beforeChild: I | TI): void,
108
- removeChild(parentInstance: I | C, child: I | TI): void,
106
+ appendChild(parentInstance: I, child: I | TI): void,
107
+ appendChildToContainer(container: C, child: I | TI): void,
108
+ insertBefore(parentInstance: I, child: I | TI, beforeChild: I | TI): void,
109
+ insertInContainerBefore(
110
+ container: C,
111
+ child: I | TI,
112
+ beforeChild: I | TI,
113
+ ): void,
114
+ removeChild(parentInstance: I, child: I | TI): void,
115
+ removeChildFromContainer(container: C, child: I | TI): void,
116
117
scheduleAnimationCallback(callback: () => void): number | void,
118
scheduleDeferredCallback(
@@ -119,7 +126,7 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
126
canHydrateInstance?: (instance: I | TI, type: T, props: P) => boolean,
127
canHydrateTextInstance?: (instance: I | TI) => boolean,
128
getNextHydratableSibling?: (instance: I | TI) => null | I | TI,
122
- getFirstHydratableChild?: (parentInstance: C | I) => null | I | TI,
129
+ getFirstHydratableChild?: (parentInstance: I | C) => null | I | TI,
130
hydrateInstance?: (
131
instance: I,
132
type: T,
src/renderers/shared/fiber/__tests__/ReactFiberHostContext-test.js
+1
-1
@@ -45,7 +45,7 @@ describe('ReactFiberHostContext', () => {
45
appendInitialChild: function() {
46
return null;
47
},
48
- appendChild: function() {
48
+ appendChildToContainer: function() {
49
return null;
50
},
51
useSyncScheduling: true,
src/renderers/testing/ReactTestRendererFiberEntry.js
+38
-31
@@ -63,6 +63,38 @@ type TextInstance = {|
63
64
const UPDATE_SIGNAL = {};
65
66
+function appendChild(
67
+ parentInstance: Instance | Container,
68
+ child: Instance | TextInstance,
69
+): void {
70
+ const index = parentInstance.children.indexOf(child);
71
+ if (index !== -1) {
72
+ parentInstance.children.splice(index, 1);
73
+ }
74
+ parentInstance.children.push(child);
75
+}
76
+
77
+function insertBefore(
78
+ parentInstance: Instance | Container,
79
+ child: Instance | TextInstance,
80
+ beforeChild: Instance | TextInstance,
81
+): void {
82
+ const index = parentInstance.children.indexOf(child);
83
+ if (index !== -1) {
84
+ parentInstance.children.splice(index, 1);
85
+ }
86
+ const beforeIndex = parentInstance.children.indexOf(beforeChild);
87
+ parentInstance.children.splice(beforeIndex, 0, child);
88
+}
89
+
90
+function removeChild(
91
+ parentInstance: Instance | Container,
92
+ child: Instance | TextInstance,
93
+): void {
94
+ const index = parentInstance.children.indexOf(child);
95
+ parentInstance.children.splice(index, 1);
96
+}
97
+
98
var TestRenderer = ReactFiberReconciler({
99
getRootHostContext() {
100
return emptyObject;
@@ -180,37 +212,12 @@ var TestRenderer = ReactFiberReconciler({
212
textInstance.text = newText;
213
},
214
183
- appendChild(
184
- parentInstance: Instance | Container,
185
- child: Instance | TextInstance,
186
- ): void {
187
- const index = parentInstance.children.indexOf(child);
188
- if (index !== -1) {
189
- parentInstance.children.splice(index, 1);
190
- }
191
- parentInstance.children.push(child);
192
- },
193
-
194
- insertBefore(
195
- parentInstance: Instance | Container,
196
- child: Instance | TextInstance,
197
- beforeChild: Instance | TextInstance,
198
- ): void {
199
- const index = parentInstance.children.indexOf(child);
200
- if (index !== -1) {
201
- parentInstance.children.splice(index, 1);
202
- }
203
- const beforeIndex = parentInstance.children.indexOf(beforeChild);
204
- parentInstance.children.splice(beforeIndex, 0, child);
205
- },
206
-
207
- removeChild(
208
- parentInstance: Instance | Container,
209
- child: Instance | TextInstance,
210
- ): void {
211
- const index = parentInstance.children.indexOf(child);
212
- parentInstance.children.splice(index, 1);
213
- },
215
+ appendChild: appendChild,
216
+ appendChildToContainer: appendChild,
217
+ insertBefore: insertBefore,
218
+ insertInContainerBefore: insertBefore,
219
+ removeChild: removeChild,
220
+ removeChildFromContainer: removeChild,
221
222
scheduleAnimationCallback(fn: Function): void {
223
setTimeout(fn);