@samitouri / QOS-React / commits / 5029c3d6bc

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);