Skip to content

Commit

Permalink
Split initial children out of createInstance
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sebmarkbage committed Nov 24, 2016
1 parent 024e2a0 commit ea34204
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 66 deletions.
31 changes: 10 additions & 21 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Expand Up @@ -13,7 +13,6 @@
'use strict';

import type { Fiber } from 'ReactFiber';
import type { HostChildren } from 'ReactFiberReconciler';
import type { ReactNodeList } from 'ReactTypes';

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

function recursivelyAppendChildren(parent : Element, child : HostChildren<Instance | TextInstance>) {
if (!child) {
return;
}
/* $FlowFixMe: Element and Text should have this property. */
if (child.nodeType === 1 || child.nodeType === 3) {
/* $FlowFixMe: Refinement issue. I don't know how to express different. */
parent.appendChild(child);
} else {
/* As a result of the refinement issue this type isn't known. */
let node : any = child;
do {
recursivelyAppendChildren(parent, node.output);
} while (node = node.sibling);
}
}

let eventsEnabled : ?boolean = null;
let selectionInformation : ?mixed = null;

Expand All @@ -93,18 +75,25 @@ var DOMRenderer = ReactFiberReconciler({
createInstance(
type : string,
props : Props,
children : HostChildren<Instance | TextInstance>,
internalInstanceHandle : Object
) : Instance {
const root = document.documentElement; // HACK

const domElement : Instance = createElement(type, props, root);
precacheFiberNode(internalInstanceHandle, domElement);
recursivelyAppendChildren(domElement, children);
setInitialProperties(domElement, type, props, root);
return domElement;
},

appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
parentInstance.appendChild(child);
},

finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
const root = document.documentElement; // HACK

setInitialProperties(domElement, type, props, root);
},

prepareUpdate(
domElement : Instance,
oldProps : Props,
Expand Down
60 changes: 21 additions & 39 deletions src/renderers/noop/ReactNoop.js
Expand Up @@ -21,7 +21,6 @@

import type { Fiber } from 'ReactFiber';
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
import type { HostChildren } from 'ReactFiberReconciler';

var ReactFiberReconciler = require('ReactFiberReconciler');
var ReactInstanceMap = require('ReactInstanceMap');
Expand All @@ -32,55 +31,35 @@ var {
var scheduledAnimationCallback = null;
var scheduledDeferredCallback = null;

const TERMINAL_TAG = 99;
const TEXT_TAG = 98;

type Container = { rootID: string, children: Array<Instance | TextInstance> };
type Props = { prop: any };
type Instance = { tag: 99, type: string, id: number, children: Array<Instance | TextInstance>, prop: any };
type TextInstance = { tag: 98, text: string };
type Instance = {| type: string, id: number, children: Array<Instance | TextInstance>, prop: any |};
type TextInstance = {| text: string, id: number |};

var instanceCounter = 0;

function recursivelyAppendChildren(
flatArray : Array<Instance | TextInstance>,
child : HostChildren<Instance | TextInstance>
) {
if (!child) {
return;
}
if (child.tag === TERMINAL_TAG || child.tag === TEXT_TAG) {
flatArray.push(child);
} else {
let node = child;
do {
recursivelyAppendChildren(flatArray, node.output);
} while (node = node.sibling);
}
}

function flattenChildren(children : HostChildren<Instance | TextInstance>) {
const flatArray = [];
recursivelyAppendChildren(flatArray, children);
return flatArray;
}

var NoopRenderer = ReactFiberReconciler({

createInstance(type : string, props : Props, children : HostChildren<Instance | TextInstance>) : Instance {
createInstance(type : string, props : Props) : Instance {
const inst = {
tag: TERMINAL_TAG,
id: instanceCounter++,
type: type,
children: flattenChildren(children),
children: [],
prop: props.prop,
};
// Hide from unit tests
Object.defineProperty(inst, 'tag', { value: inst.tag, enumerable: false });
Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
return inst;
},

appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
parentInstance.children.push(child);
},

finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
// Noop
},

prepareUpdate(instance : Instance, oldProps : Props, newProps : Props) : boolean {
return true;
},
Expand All @@ -90,9 +69,9 @@ var NoopRenderer = ReactFiberReconciler({
},

createTextInstance(text : string) : TextInstance {
var inst = { tag: TEXT_TAG, text : text };
var inst = { text : text, id: instanceCounter++ };
// Hide from unit tests
Object.defineProperty(inst, 'tag', { value: inst.tag, enumerable: false });
Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
return inst;
},

Expand Down Expand Up @@ -209,7 +188,7 @@ var ReactNoop = {
}
// Unsound duck typing.
const component = (componentOrElement : any);
if (component.tag === TERMINAL_TAG || component.tag === TEXT_TAG) {
if (typeof component.id === 'number') {
return component;
}
const inst = ReactInstanceMap.get(component);
Expand Down Expand Up @@ -274,10 +253,13 @@ var ReactNoop = {
function logHostInstances(children: Array<Instance | TextInstance>, depth) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.tag === TEXT_TAG) {
log(' '.repeat(depth) + '- ' + child.text);
var indent = ' '.repeat(depth);
if (typeof child.text === 'string') {
log(indent + '- ' + child.text);
} else {
log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
// $FlowFixMe - The child should've been refined now.
log(indent + '- ' + child.type + '#' + child.id);
// $FlowFixMe - The child should've been refined now.
logHostInstances(child.children, depth + 1);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/renderers/shared/fiber/ReactFiberCommitWork.js
Expand Up @@ -210,8 +210,10 @@ module.exports = function<T, P, I, TI, C>(
// When we go into a portal, it becomes the parent to remove from.
// We will reassign it back when we pop the portal on the way up.
parent = node.stateNode.containerInfo;
node = node.child;
continue;
if (node.child) {
node = node.child;
continue;
}
} else {
commitUnmount(node);
if (node.child) {
Expand Down
44 changes: 41 additions & 3 deletions src/renderers/shared/fiber/ReactFiberCompleteWork.js
Expand Up @@ -46,6 +46,8 @@ var {
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {

const createInstance = config.createInstance;
const appendInitialChild = config.appendInitialChild;
const finalizeInitialChildren = config.finalizeInitialChildren;
const createTextInstance = config.createTextInstance;
const prepareUpdate = config.prepareUpdate;

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

function appendAllChildren(parent : I, workInProgress : Fiber) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
let node = workInProgress.child;
while (node) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (node.tag === Portal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child) {
// TODO: Coroutines need to visit the stateNode.
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (!node.sibling) {
if (!node.return || node.return === workInProgress) {
return;
}
node = node.return;
}
node = node.sibling;
}
}

function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
switch (workInProgress.tag) {
case FunctionalComponent:
Expand Down Expand Up @@ -201,9 +232,16 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
return null;
}
}
const child = workInProgress.child;
const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
const instance = createInstance(workInProgress.type, newProps, children, workInProgress);

// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
// Finally, finalizeInitialChildren here in completeWork.
const instance = createInstance(workInProgress.type, newProps, workInProgress);
appendAllChildren(instance, workInProgress);
finalizeInitialChildren(instance, workInProgress.type, newProps);

// TODO: This seems like unnecessary duplication.
workInProgress.stateNode = instance;
workInProgress.output = instance;
Expand Down
5 changes: 4 additions & 1 deletion src/renderers/shared/fiber/ReactFiberReconciler.js
Expand Up @@ -47,7 +47,10 @@ type OpaqueNode = Fiber;

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

createInstance(type : T, props : P, children : HostChildren<I | TI>, internalInstanceHandle : OpaqueNode) : I,
createInstance(type : T, props : P, internalInstanceHandle : OpaqueNode) : I,
appendInitialChild(parentInstance : I, child : I) : void,
finalizeInitialChildren(parentInstance : I, type : T, props : P) : void,

prepareUpdate(instance : I, oldProps : P, newProps : P) : boolean,
commitUpdate(instance : I, oldProps : P, newProps : P, internalInstanceHandle : OpaqueNode) : void,

Expand Down

0 comments on commit ea34204

Please sign in to comment.