@samitouri / QOS-React / commits / ea3420471f

Split initial children out of createInstance

The goal of this is to avoid passing an opaque data structure that needs to be recursively searched by the host. I considered having some helper for doing the recursion but I figured it might be helpful to let the reconciler move this around. For example we might want to create an instance in beginWork and add to it as we go. This would let us avoid traversing the tree twice and would solve the IE11 perf issue. So instead, we create the instance first then call appendChild. I could just call the normal one but I figured that I would make a special one just in case. For example if you wanted to perform commits on a separate thread from creation. This turned out to be useful in ReactNoop where I can avoid searching the array for an existing one since I know the child isn't there already. (Although splitting placement into insertion/move might be better.) Finally, we need the ability to update an instance after all the children have been insertion. Such as `<select value={...} />`. I called this finalizeInitialChildren.

Sebastian Markbage committed Nov 23, 2016 at 17:38 UTC ea3420471f0e79b2212b95159d6959d221a52ca1
5 files changed +80 -66
src/renderers/dom/fiber/ReactDOMFiber.js
+10 -21
@@ -13,7 +13,6 @@
13 'use strict';
14
15 import type { Fiber } from 'ReactFiber';
16 -import type { HostChildren } from 'ReactFiberReconciler';
16 import type { ReactNodeList } from 'ReactTypes';
17
18 var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
@@ -55,23 +54,6 @@ type Props = { className ?: string };
54 type Instance = Element;
55 type TextInstance = Text;
56
58 -function recursivelyAppendChildren(parent : Element, child : HostChildren<Instance | TextInstance>) {
59 - if (!child) {
60 - return;
61 - }
62 - /* $FlowFixMe: Element and Text should have this property. */
63 - if (child.nodeType === 1 || child.nodeType === 3) {
64 - /* $FlowFixMe: Refinement issue. I don't know how to express different. */
65 - parent.appendChild(child);
66 - } else {
67 - /* As a result of the refinement issue this type isn't known. */
68 - let node : any = child;
69 - do {
70 - recursivelyAppendChildren(parent, node.output);
71 - } while (node = node.sibling);
72 - }
73 -}
74 -
57 let eventsEnabled : ?boolean = null;
58 let selectionInformation : ?mixed = null;
59
@@ -93,18 +75,25 @@ var DOMRenderer = ReactFiberReconciler({
75 createInstance(
76 type : string,
77 props : Props,
96 - children : HostChildren<Instance | TextInstance>,
78 internalInstanceHandle : Object
79 ) : Instance {
80 const root = document.documentElement; // HACK
81
82 const domElement : Instance = createElement(type, props, root);
83 precacheFiberNode(internalInstanceHandle, domElement);
103 - recursivelyAppendChildren(domElement, children);
104 - setInitialProperties(domElement, type, props, root);
84 return domElement;
85 },
86
87 + appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
88 + parentInstance.appendChild(child);
89 + },
90 +
91 + finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
92 + const root = document.documentElement; // HACK
93 +
94 + setInitialProperties(domElement, type, props, root);
95 + },
96 +
97 prepareUpdate(
98 domElement : Instance,
99 oldProps : Props,
src/renderers/noop/ReactNoop.js
+21 -39
@@ -21,7 +21,6 @@
21
22 import type { Fiber } from 'ReactFiber';
23 import type { UpdateQueue } from 'ReactFiberUpdateQueue';
24 -import type { HostChildren } from 'ReactFiberReconciler';
24
25 var ReactFiberReconciler = require('ReactFiberReconciler');
26 var ReactInstanceMap = require('ReactInstanceMap');
@@ -32,55 +31,35 @@ var {
31 var scheduledAnimationCallback = null;
32 var scheduledDeferredCallback = null;
33
35 -const TERMINAL_TAG = 99;
36 -const TEXT_TAG = 98;
37 -
34 type Container = { rootID: string, children: Array<Instance | TextInstance> };
35 type Props = { prop: any };
40 -type Instance = { tag: 99, type: string, id: number, children: Array<Instance | TextInstance>, prop: any };
41 -type TextInstance = { tag: 98, text: string };
36 +type Instance = {| type: string, id: number, children: Array<Instance | TextInstance>, prop: any |};
37 +type TextInstance = {| text: string, id: number |};
38
39 var instanceCounter = 0;
40
45 -function recursivelyAppendChildren(
46 - flatArray : Array<Instance | TextInstance>,
47 - child : HostChildren<Instance | TextInstance>
48 -) {
49 - if (!child) {
50 - return;
51 - }
52 - if (child.tag === TERMINAL_TAG || child.tag === TEXT_TAG) {
53 - flatArray.push(child);
54 - } else {
55 - let node = child;
56 - do {
57 - recursivelyAppendChildren(flatArray, node.output);
58 - } while (node = node.sibling);
59 - }
60 -}
61 -
62 -function flattenChildren(children : HostChildren<Instance | TextInstance>) {
63 - const flatArray = [];
64 - recursivelyAppendChildren(flatArray, children);
65 - return flatArray;
66 -}
67 -
41 var NoopRenderer = ReactFiberReconciler({
42
70 - createInstance(type : string, props : Props, children : HostChildren<Instance | TextInstance>) : Instance {
43 + createInstance(type : string, props : Props) : Instance {
44 const inst = {
72 - tag: TERMINAL_TAG,
45 id: instanceCounter++,
46 type: type,
75 - children: flattenChildren(children),
47 + children: [],
48 prop: props.prop,
49 };
50 // Hide from unit tests
79 - Object.defineProperty(inst, 'tag', { value: inst.tag, enumerable: false });
51 Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
52 return inst;
53 },
54
55 + appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
56 + parentInstance.children.push(child);
57 + },
58 +
59 + finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
60 + // Noop
61 + },
62 +
63 prepareUpdate(instance : Instance, oldProps : Props, newProps : Props) : boolean {
64 return true;
65 },
@@ -90,9 +69,9 @@ var NoopRenderer = ReactFiberReconciler({
69 },
70
71 createTextInstance(text : string) : TextInstance {
93 - var inst = { tag: TEXT_TAG, text : text };
72 + var inst = { text : text, id: instanceCounter++ };
73 // Hide from unit tests
95 - Object.defineProperty(inst, 'tag', { value: inst.tag, enumerable: false });
74 + Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
75 return inst;
76 },
77
@@ -209,7 +188,7 @@ var ReactNoop = {
188 }
189 // Unsound duck typing.
190 const component = (componentOrElement : any);
212 - if (component.tag === TERMINAL_TAG || component.tag === TEXT_TAG) {
191 + if (typeof component.id === 'number') {
192 return component;
193 }
194 const inst = ReactInstanceMap.get(component);
@@ -274,10 +253,13 @@ var ReactNoop = {
253 function logHostInstances(children: Array<Instance | TextInstance>, depth) {
254 for (var i = 0; i < children.length; i++) {
255 var child = children[i];
277 - if (child.tag === TEXT_TAG) {
278 - log(' '.repeat(depth) + '- ' + child.text);
256 + var indent = ' '.repeat(depth);
257 + if (typeof child.text === 'string') {
258 + log(indent + '- ' + child.text);
259 } else {
280 - log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
260 + // $FlowFixMe - The child should've been refined now.
261 + log(indent + '- ' + child.type + '#' + child.id);
262 + // $FlowFixMe - The child should've been refined now.
263 logHostInstances(child.children, depth + 1);
264 }
265 }
src/renderers/shared/fiber/ReactFiberCommitWork.js
+4 -2
@@ -210,8 +210,10 @@ module.exports = function<T, P, I, TI, C>(
210 // When we go into a portal, it becomes the parent to remove from.
211 // We will reassign it back when we pop the portal on the way up.
212 parent = node.stateNode.containerInfo;
213 - node = node.child;
214 - continue;
213 + if (node.child) {
214 + node = node.child;
215 + continue;
216 + }
217 } else {
218 commitUnmount(node);
219 if (node.child) {
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+41 -3
@@ -46,6 +46,8 @@ var {
46 module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
47
48 const createInstance = config.createInstance;
49 + const appendInitialChild = config.appendInitialChild;
50 + const finalizeInitialChildren = config.finalizeInitialChildren;
51 const createTextInstance = config.createTextInstance;
52 const prepareUpdate = config.prepareUpdate;
53
@@ -124,6 +126,35 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
126 return workInProgress.stateNode;
127 }
128
129 + function appendAllChildren(parent : I, workInProgress : Fiber) {
130 + // We only have the top Fiber that was created but we need recurse down its
131 + // children to find all the terminal nodes.
132 + let node = workInProgress.child;
133 + while (node) {
134 + if (node.tag === HostComponent || node.tag === HostText) {
135 + appendInitialChild(parent, node.stateNode);
136 + } else if (node.tag === Portal) {
137 + // If we have a portal child, then we don't want to traverse
138 + // down its children. Instead, we'll get insertions from each child in
139 + // the portal directly.
140 + } else if (node.child) {
141 + // TODO: Coroutines need to visit the stateNode.
142 + node = node.child;
143 + continue;
144 + }
145 + if (node === workInProgress) {
146 + return;
147 + }
148 + while (!node.sibling) {
149 + if (!node.return || node.return === workInProgress) {
150 + return;
151 + }
152 + node = node.return;
153 + }
154 + node = node.sibling;
155 + }
156 + }
157 +
158 function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
159 switch (workInProgress.tag) {
160 case FunctionalComponent:
@@ -201,9 +232,16 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
232 return null;
233 }
234 }
204 - const child = workInProgress.child;
205 - const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
206 - const instance = createInstance(workInProgress.type, newProps, children, workInProgress);
235 +
236 + // TODO: Move createInstance to beginWork and keep it on a context
237 + // "stack" as the parent. Then append children as we go in beginWork
238 + // or completeWork depending on we want to add then top->down or
239 + // bottom->up. Top->down is faster in IE11.
240 + // Finally, finalizeInitialChildren here in completeWork.
241 + const instance = createInstance(workInProgress.type, newProps, workInProgress);
242 + appendAllChildren(instance, workInProgress);
243 + finalizeInitialChildren(instance, workInProgress.type, newProps);
244 +
245 // TODO: This seems like unnecessary duplication.
246 workInProgress.stateNode = instance;
247 workInProgress.output = instance;
src/renderers/shared/fiber/ReactFiberReconciler.js
+4 -1
@@ -47,7 +47,10 @@ type OpaqueNode = Fiber;
47
48 export type HostConfig<T, P, I, TI, C> = {
49
50 - createInstance(type : T, props : P, children : HostChildren<I | TI>, internalInstanceHandle : OpaqueNode) : I,
50 + createInstance(type : T, props : P, internalInstanceHandle : OpaqueNode) : I,
51 + appendInitialChild(parentInstance : I, child : I) : void,
52 + finalizeInitialChildren(parentInstance : I, type : T, props : P) : void,
53 +
54 prepareUpdate(instance : I, oldProps : P, newProps : P) : boolean,
55 commitUpdate(instance : I, oldProps : P, newProps : P, internalInstanceHandle : OpaqueNode) : void,
56