@samitouri / QOS-React / commits / 78add2dc63

Update root children using the appendChild/insertBefore/removeChild methods

This removes updateContainer and instead uses the regular child mutation methods to insert into the root container and portals. Since we're no longer clearing out the container DOM in updateContainer we have to do that manually during initial mount. This now works on a document and one of the tests end up unmounting the body when you render into the document so I had to work around that bit since we don't yet properly support rendering into the document root.

Sebastian Markbage committed Nov 23, 2016 at 15:30 UTC 78add2dc639e4a8aaa9ea77bec64e45fac6aa11b
8 files changed +76 -60
scripts/fiber/tests-failing.txt
-1
@@ -120,4 +120,3 @@ src/renderers/shared/stack/reconciler/__tests__/refs-test.js
120
121 src/test/__tests__/ReactTestUtils-test.js
122 * traverses children in the correct order
123 -* should support injected wrapper components as DOM components
scripts/fiber/tests-passing.txt
+1
@@ -1560,6 +1560,7 @@ src/test/__tests__/ReactTestUtils-test.js
1560 * can scryRenderedDOMComponentsWithClass with TextComponent
1561 * can scryRenderedDOMComponentsWithClass with className contains \n
1562 * can scryRenderedDOMComponentsWithClass with multiple classes
1563 +* should support injected wrapper components as DOM components
1564 * should change the value of an input field
1565 * should change the value of an input field in a component
1566 * should throw when attempting to use ReactTestUtils.Simulate with shallow rendering
src/renderers/dom/fiber/ReactDOMFiber.js
+16 -14
@@ -38,6 +38,8 @@ var {
38 } = ReactDOMFiberComponent;
39 var { precacheFiberNode } = ReactDOMComponentTree;
40
41 +const DOCUMENT_NODE = 9;
42 +
43 ReactDOMInjection.inject();
44 ReactControlledComponent.injection.injectFiberControlledHostComponent(
45 ReactDOMFiberComponent
@@ -88,19 +90,13 @@ var DOMRenderer = ReactFiberReconciler({
90 eventsEnabled = null;
91 },
92
91 - updateContainer(container : Container, children : HostChildren<Instance | TextInstance>) : void {
92 - // TODO: Containers should update similarly to other parents.
93 - container.innerHTML = '';
94 - recursivelyAppendChildren(container, children);
95 - },
96 -
93 createInstance(
94 type : string,
95 props : Props,
96 children : HostChildren<Instance | TextInstance>,
97 internalInstanceHandle : Object
98 ) : Instance {
103 - const root = document.body; // HACK
99 + const root = document.documentElement; // HACK
100
101 const domElement : Instance = createElement(type, props, root);
102 precacheFiberNode(internalInstanceHandle, domElement);
@@ -124,7 +120,7 @@ var DOMRenderer = ReactFiberReconciler({
120 internalInstanceHandle : Object
121 ) : void {
122 var type = domElement.tagName.toLowerCase(); // HACK
127 - var root = document.body; // HACK
123 + var root = document.documentElement; // HACK
124 // Update the internal instance handle so that we know which props are
125 // the current ones.
126 precacheFiberNode(internalInstanceHandle, domElement);
@@ -141,19 +137,19 @@ var DOMRenderer = ReactFiberReconciler({
137 textInstance.nodeValue = newText;
138 },
139
144 - appendChild(parentInstance : Instance, child : Instance | TextInstance) : void {
140 + appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
141 parentInstance.appendChild(child);
142 },
143
144 insertBefore(
149 - parentInstance : Instance,
145 + parentInstance : Instance | Container,
146 child : Instance | TextInstance,
147 beforeChild : Instance | TextInstance
148 ) : void {
149 parentInstance.insertBefore(child, beforeChild);
150 },
151
156 - removeChild(parentInstance : Instance, child : Instance | TextInstance) : void {
152 + removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
153 parentInstance.removeChild(child);
154 },
155
@@ -177,9 +173,15 @@ function warnAboutUnstableUse() {
173 warned = true;
174 }
175
180 -function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
176 +function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
177 + let container : DOMContainerElement =
178 + containerNode.nodeType === DOCUMENT_NODE ? (containerNode : any).documentElement : (containerNode : any);
179 let root;
180 if (!container._reactRootContainer) {
181 + // First clear any existing content.
182 + while (container.lastChild) {
183 + container.removeChild(container.lastChild);
184 + }
185 root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
186 } else {
187 DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
@@ -194,12 +196,12 @@ var ReactDOM = {
196 return renderSubtreeIntoContainer(null, element, container, callback);
197 },
198
197 - unstable_renderSubtreeIntoContainer(parentComponent : ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
199 + unstable_renderSubtreeIntoContainer(parentComponent : ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
200 invariant(
201 parentComponent != null && ReactInstanceMap.has(parentComponent),
202 'parentComponent must be a valid React Component'
203 );
202 - return renderSubtreeIntoContainer(parentComponent, element, container, callback);
204 + return renderSubtreeIntoContainer(parentComponent, element, containerNode, callback);
205 },
206
207 unmountComponentAtNode(container : DOMContainerElement) {
src/renderers/noop/ReactNoop.js
+3 -7
@@ -67,10 +67,6 @@ function flattenChildren(children : HostChildren<Instance | TextInstance>) {
67
68 var NoopRenderer = ReactFiberReconciler({
69
70 - updateContainer(containerInfo : Container, children : HostChildren<Instance | TextInstance>) : void {
71 - containerInfo.children = flattenChildren(children);
72 - },
73 -
70 createInstance(type : string, props : Props, children : HostChildren<Instance | TextInstance>) : Instance {
71 const inst = {
72 tag: TERMINAL_TAG,
@@ -104,7 +100,7 @@ var NoopRenderer = ReactFiberReconciler({
100 textInstance.text = newText;
101 },
102
107 - appendChild(parentInstance : Instance, child : Instance | TextInstance) : void {
103 + appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
104 const index = parentInstance.children.indexOf(child);
105 if (index !== -1) {
106 parentInstance.children.splice(index, 1);
@@ -113,7 +109,7 @@ var NoopRenderer = ReactFiberReconciler({
109 },
110
111 insertBefore(
116 - parentInstance : Instance,
112 + parentInstance : Instance | Container,
113 child : Instance | TextInstance,
114 beforeChild : Instance | TextInstance
115 ) : void {
@@ -128,7 +124,7 @@ var NoopRenderer = ReactFiberReconciler({
124 parentInstance.children.splice(beforeIndex, 0, child);
125 },
126
131 - removeChild(parentInstance : Instance, child : Instance | TextInstance) : void {
127 + removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
128 const index = parentInstance.children.indexOf(child);
129 if (index === -1) {
130 throw new Error('This child does not exist.');
src/renderers/shared/fiber/ReactFiberBeginWork.js
+18 -1
@@ -300,7 +300,24 @@ module.exports = function<T, P, I, TI, C>(
300 }
301
302 function updatePortalComponent(current, workInProgress) {
303 - reconcileChildren(current, workInProgress, workInProgress.pendingProps);
303 + const priorityLevel = workInProgress.pendingWorkPriority;
304 + const nextChildren = workInProgress.pendingProps;
305 + if (!current) {
306 + // Portals are special because we don't append the children during mount
307 + // but at commit. Therefore we need to track insertions which the normal
308 + // flow doesn't do during mount. This doesn't happen at the root because
309 + // the root always starts with a "current" with a null child.
310 + // TODO: Consider unifying this with how the root works.
311 + workInProgress.child = reconcileChildFibersInPlace(
312 + workInProgress,
313 + workInProgress.child,
314 + nextChildren,
315 + priorityLevel
316 + );
317 + markChildAsProgressed(current, workInProgress, priorityLevel);
318 + } else {
319 + reconcileChildren(current, workInProgress, nextChildren);
320 + }
321 }
322
323 /*
src/renderers/shared/fiber/ReactFiberCommitWork.js
+32 -23
@@ -13,7 +13,6 @@
13 'use strict';
14
15 import type { Fiber } from 'ReactFiber';
16 -import type { FiberRoot } from 'ReactFiberRoot';
16 import type { HostConfig } from 'ReactFiberReconciler';
17
18 var ReactTypeOfWork = require('ReactTypeOfWork');
@@ -37,7 +36,6 @@ module.exports = function<T, P, I, TI, C>(
36 trapError : (failedFiber : Fiber, error: Error, isUnmounting : boolean) => void
37 ) {
38
40 - const updateContainer = config.updateContainer;
39 const commitUpdate = config.commitUpdate;
40 const commitTextUpdate = config.commitTextUpdate;
41
@@ -68,22 +66,30 @@ module.exports = function<T, P, I, TI, C>(
66 }
67 }
68
71 - function getHostParent(fiber : Fiber) : ?I {
69 + function getHostParent(fiber : Fiber) : null | I | C {
70 let parent = fiber.return;
71 while (parent) {
72 switch (parent.tag) {
73 case HostComponent:
74 return parent.stateNode;
75 case HostContainer:
78 - // TODO: Currently we use the updateContainer feature to update these,
79 - // but we should be able to handle this case too.
80 - return null;
76 + return parent.stateNode.containerInfo;
77 + case Portal:
78 + return parent.stateNode.containerInfo;
79 }
80 parent = parent.return;
81 }
82 return null;
83 }
84
85 + function isHostParent(fiber : Fiber) : boolean {
86 + return (
87 + fiber.tag === HostComponent ||
88 + fiber.tag === HostContainer ||
89 + fiber.tag === Portal
90 + );
91 + }
92 +
93 function getHostSibling(fiber : Fiber) : ?I {
94 // We're going to search forward into the tree until we find a sibling host
95 // node. Unfortunately, if multiple insertions are done in a row we have to
@@ -93,7 +99,7 @@ module.exports = function<T, P, I, TI, C>(
99 siblings: while (true) {
100 // If we didn't find anything, let's try the next sibling.
101 while (!node.sibling) {
96 - if (!node.return || node.return.tag === HostComponent) {
102 + if (!node.return || isHostParent(node.return)) {
103 // If we pop out of the root or hit the parent the fiber we are the
104 // last sibling.
105 return null;
@@ -142,6 +148,10 @@ module.exports = function<T, P, I, TI, C>(
148 } else {
149 appendChild(parent, node.stateNode);
150 }
151 + } else if (node.tag === Portal) {
152 + // If the insertion itself is a portal, then we don't want to traverse
153 + // down its children. Instead, we'll get insertions from each child in
154 + // the portal directly.
155 } else if (node.child) {
156 // TODO: Coroutines need to visit the stateNode.
157 node = node.child;
@@ -201,6 +211,18 @@ module.exports = function<T, P, I, TI, C>(
211 if (parent) {
212 removeChild(parent, node.stateNode);
213 }
214 + } else if (node.tag === Portal) {
215 + // If this is a portal, then the parent is actually the portal itself.
216 + // We need to keep track of which parent we're removing from.
217 + // TODO: This uses a recursive call. We can get rid of that by mutating
218 + // the parent binding and restoring it by searching for the host parent
219 + // again when we pop past a portal.
220 + const portalParent = node.stateNode.containerInfo;
221 + let child = node.child;
222 + while (child) {
223 + unmountHostComponents(portalParent, child);
224 + child = child.sibling;
225 + }
226 } else {
227 commitUnmount(node);
228 if (node.child) {
@@ -260,11 +282,6 @@ module.exports = function<T, P, I, TI, C>(
282 detachRef(current);
283 return;
284 }
263 - case Portal: {
264 - const containerInfo : C = current.stateNode.containerInfo;
265 - updateContainer(containerInfo, null);
266 - return;
267 - }
285 }
286 }
287
@@ -274,14 +291,6 @@ module.exports = function<T, P, I, TI, C>(
291 detachRefIfNeeded(current, finishedWork);
292 return;
293 }
277 - case HostContainer: {
278 - // TODO: Attach children to root container.
279 - const children = finishedWork.output;
280 - const root : FiberRoot = finishedWork.stateNode;
281 - const containerInfo : C = root.containerInfo;
282 - updateContainer(containerInfo, children);
283 - return;
284 - }
294 case HostComponent: {
295 const instance : I = finishedWork.stateNode;
296 if (instance != null && current) {
@@ -303,10 +312,10 @@ module.exports = function<T, P, I, TI, C>(
312 commitTextUpdate(textInstance, oldText, newText);
313 return;
314 }
315 + case HostContainer: {
316 + return;
317 + }
318 case Portal: {
307 - const children = finishedWork.child;
308 - const containerInfo : C = finishedWork.stateNode.containerInfo;
309 - updateContainer(containerInfo, children);
319 return;
320 }
321 default:
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+3 -5
@@ -167,11 +167,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
167 fiberRoot.context = fiberRoot.pendingContext;
168 fiberRoot.pendingContext = null;
169 }
170 - // We don't know if a container has updated any children so we always
171 - // need to update it right now. We schedule this side-effect before
172 - // all the other side-effects in the subtree. We need to schedule it
173 - // before so that the entire tree is up-to-date before the life-cycles
174 - // are invoked.
170 + // TODO: Only mark this as an update if we have any pending callbacks
171 + // on it.
172 markUpdate(workInProgress);
173 return null;
174 }
@@ -253,6 +250,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
250 transferOutput(workInProgress.child, workInProgress);
251 return null;
252 case Portal:
253 + // TODO: Only mark this as an update if we have any pending callbacks.
254 markUpdate(workInProgress);
255 workInProgress.output = null;
256 workInProgress.memoizedProps = workInProgress.pendingProps;
src/renderers/shared/fiber/ReactFiberReconciler.js
+3 -9
@@ -47,12 +47,6 @@ type OpaqueNode = Fiber;
47
48 export type HostConfig<T, P, I, TI, C> = {
49
50 - // TODO: We don't currently have a quick way to detect that children didn't
51 - // reorder so we host will always need to check the set. We should make a flag
52 - // or something so that it can bailout easily.
53 -
54 - updateContainer(containerInfo : C, children : HostChildren<I | TI>) : void,
55 -
50 createInstance(type : T, props : P, children : HostChildren<I | TI>, internalInstanceHandle : OpaqueNode) : I,
51 prepareUpdate(instance : I, oldProps : P, newProps : P) : boolean,
52 commitUpdate(instance : I, oldProps : P, newProps : P, internalInstanceHandle : OpaqueNode) : void,
@@ -60,9 +54,9 @@ export type HostConfig<T, P, I, TI, C> = {
54 createTextInstance(text : string, internalInstanceHandle : OpaqueNode) : TI,
55 commitTextUpdate(textInstance : TI, oldText : string, newText : string) : void,
56
63 - appendChild(parentInstance : I, child : I | TI) : void,
64 - insertBefore(parentInstance : I, child : I | TI, beforeChild : I | TI) : void,
65 - removeChild(parentInstance : I, child : I | TI) : void,
57 + appendChild(parentInstance : I | C, child : I | TI) : void,
58 + insertBefore(parentInstance : I | C, child : I | TI, beforeChild : I | TI) : void,
59 + removeChild(parentInstance : I | C, child : I | TI) : void,
60
61 scheduleAnimationCallback(callback : () => void) : void,
62 scheduleDeferredCallback(callback : (deadline : Deadline) => void) : void,