Skip to content

Commit ea34204

Browse files
committed
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.
1 parent 024e2a0 commit ea34204

File tree

5 files changed

+80
-66
lines changed

5 files changed

+80
-66
lines changed

src/renderers/dom/fiber/ReactDOMFiber.js

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
'use strict';
1414

1515
import type { Fiber } from 'ReactFiber';
16-
import type { HostChildren } from 'ReactFiberReconciler';
1716
import type { ReactNodeList } from 'ReactTypes';
1817

1918
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
@@ -55,23 +54,6 @@ type Props = { className ?: string };
5554
type Instance = Element;
5655
type TextInstance = Text;
5756

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-
7557
let eventsEnabled : ?boolean = null;
7658
let selectionInformation : ?mixed = null;
7759

@@ -93,18 +75,25 @@ var DOMRenderer = ReactFiberReconciler({
9375
createInstance(
9476
type : string,
9577
props : Props,
96-
children : HostChildren<Instance | TextInstance>,
9778
internalInstanceHandle : Object
9879
) : Instance {
9980
const root = document.documentElement; // HACK
10081

10182
const domElement : Instance = createElement(type, props, root);
10283
precacheFiberNode(internalInstanceHandle, domElement);
103-
recursivelyAppendChildren(domElement, children);
104-
setInitialProperties(domElement, type, props, root);
10584
return domElement;
10685
},
10786

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+
10897
prepareUpdate(
10998
domElement : Instance,
11099
oldProps : Props,

src/renderers/noop/ReactNoop.js

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import type { Fiber } from 'ReactFiber';
2323
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
24-
import type { HostChildren } from 'ReactFiberReconciler';
2524

2625
var ReactFiberReconciler = require('ReactFiberReconciler');
2726
var ReactInstanceMap = require('ReactInstanceMap');
@@ -32,55 +31,35 @@ var {
3231
var scheduledAnimationCallback = null;
3332
var scheduledDeferredCallback = null;
3433

35-
const TERMINAL_TAG = 99;
36-
const TEXT_TAG = 98;
37-
3834
type Container = { rootID: string, children: Array<Instance | TextInstance> };
3935
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 |};
4238

4339
var instanceCounter = 0;
4440

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-
6841
var NoopRenderer = ReactFiberReconciler({
6942

70-
createInstance(type : string, props : Props, children : HostChildren<Instance | TextInstance>) : Instance {
43+
createInstance(type : string, props : Props) : Instance {
7144
const inst = {
72-
tag: TERMINAL_TAG,
7345
id: instanceCounter++,
7446
type: type,
75-
children: flattenChildren(children),
47+
children: [],
7648
prop: props.prop,
7749
};
7850
// Hide from unit tests
79-
Object.defineProperty(inst, 'tag', { value: inst.tag, enumerable: false });
8051
Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
8152
return inst;
8253
},
8354

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+
8463
prepareUpdate(instance : Instance, oldProps : Props, newProps : Props) : boolean {
8564
return true;
8665
},
@@ -90,9 +69,9 @@ var NoopRenderer = ReactFiberReconciler({
9069
},
9170

9271
createTextInstance(text : string) : TextInstance {
93-
var inst = { tag: TEXT_TAG, text : text };
72+
var inst = { text : text, id: instanceCounter++ };
9473
// Hide from unit tests
95-
Object.defineProperty(inst, 'tag', { value: inst.tag, enumerable: false });
74+
Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
9675
return inst;
9776
},
9877

@@ -209,7 +188,7 @@ var ReactNoop = {
209188
}
210189
// Unsound duck typing.
211190
const component = (componentOrElement : any);
212-
if (component.tag === TERMINAL_TAG || component.tag === TEXT_TAG) {
191+
if (typeof component.id === 'number') {
213192
return component;
214193
}
215194
const inst = ReactInstanceMap.get(component);
@@ -274,10 +253,13 @@ var ReactNoop = {
274253
function logHostInstances(children: Array<Instance | TextInstance>, depth) {
275254
for (var i = 0; i < children.length; i++) {
276255
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);
279259
} 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.
281263
logHostInstances(child.children, depth + 1);
282264
}
283265
}

src/renderers/shared/fiber/ReactFiberCommitWork.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,10 @@ module.exports = function<T, P, I, TI, C>(
210210
// When we go into a portal, it becomes the parent to remove from.
211211
// We will reassign it back when we pop the portal on the way up.
212212
parent = node.stateNode.containerInfo;
213-
node = node.child;
214-
continue;
213+
if (node.child) {
214+
node = node.child;
215+
continue;
216+
}
215217
} else {
216218
commitUnmount(node);
217219
if (node.child) {

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ var {
4646
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
4747

4848
const createInstance = config.createInstance;
49+
const appendInitialChild = config.appendInitialChild;
50+
const finalizeInitialChildren = config.finalizeInitialChildren;
4951
const createTextInstance = config.createTextInstance;
5052
const prepareUpdate = config.prepareUpdate;
5153

@@ -124,6 +126,35 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
124126
return workInProgress.stateNode;
125127
}
126128

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+
127158
function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
128159
switch (workInProgress.tag) {
129160
case FunctionalComponent:
@@ -201,9 +232,16 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
201232
return null;
202233
}
203234
}
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+
207245
// TODO: This seems like unnecessary duplication.
208246
workInProgress.stateNode = instance;
209247
workInProgress.output = instance;

src/renderers/shared/fiber/ReactFiberReconciler.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ type OpaqueNode = Fiber;
4747

4848
export type HostConfig<T, P, I, TI, C> = {
4949

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+
5154
prepareUpdate(instance : I, oldProps : P, newProps : P) : boolean,
5255
commitUpdate(instance : I, oldProps : P, newProps : P, internalInstanceHandle : OpaqueNode) : void,
5356

0 commit comments

Comments
 (0)