Skip to content

Commit

Permalink
Merge pull request #8400 from sebmarkbage/fibercontainerchildren
Browse files Browse the repository at this point in the history
[Fiber] Update root children using appendChild/insertBefore/removeChild
  • Loading branch information
sebmarkbage authored Nov 24, 2016
2 parents 8791325 + ea34204 commit 7d2be2c
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 129 deletions.
1 change: 0 additions & 1 deletion scripts/fiber/tests-failing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,3 @@ src/renderers/shared/stack/reconciler/__tests__/refs-test.js

src/test/__tests__/ReactTestUtils-test.js
* traverses children in the correct order
* should support injected wrapper components as DOM components
1 change: 1 addition & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,7 @@ src/test/__tests__/ReactTestUtils-test.js
* can scryRenderedDOMComponentsWithClass with TextComponent
* can scryRenderedDOMComponentsWithClass with className contains \n
* can scryRenderedDOMComponentsWithClass with multiple classes
* should support injected wrapper components as DOM components
* should change the value of an input field
* should change the value of an input field in a component
* should throw when attempting to use ReactTestUtils.Simulate with shallow rendering
Expand Down
61 changes: 26 additions & 35 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
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 All @@ -39,6 +38,8 @@ var {
} = ReactDOMFiberComponent;
var { precacheFiberNode } = ReactDOMComponentTree;

const DOCUMENT_NODE = 9;

ReactDOMInjection.inject();
ReactControlledComponent.injection.injectFiberControlledHostComponent(
ReactDOMFiberComponent
Expand All @@ -54,23 +55,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 @@ -89,27 +73,28 @@ var DOMRenderer = ReactFiberReconciler({
eventsEnabled = null;
},

updateContainer(container : Container, children : HostChildren<Instance | TextInstance>) : void {
// TODO: Containers should update similarly to other parents.
container.innerHTML = '';
recursivelyAppendChildren(container, children);
},

createInstance(
type : string,
props : Props,
children : HostChildren<Instance | TextInstance>,
internalInstanceHandle : Object
) : Instance {
const root = document.body; // HACK
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 All @@ -125,7 +110,7 @@ var DOMRenderer = ReactFiberReconciler({
internalInstanceHandle : Object
) : void {
var type = domElement.tagName.toLowerCase(); // HACK
var root = document.body; // HACK
var root = document.documentElement; // HACK
// Update the internal instance handle so that we know which props are
// the current ones.
precacheFiberNode(internalInstanceHandle, domElement);
Expand All @@ -142,19 +127,19 @@ var DOMRenderer = ReactFiberReconciler({
textInstance.nodeValue = newText;
},

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

insertBefore(
parentInstance : Instance,
parentInstance : Instance | Container,
child : Instance | TextInstance,
beforeChild : Instance | TextInstance
) : void {
parentInstance.insertBefore(child, beforeChild);
},

removeChild(parentInstance : Instance, child : Instance | TextInstance) : void {
removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
parentInstance.removeChild(child);
},

Expand All @@ -180,9 +165,15 @@ function warnAboutUnstableUse() {
warned = true;
}

function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
let container : DOMContainerElement =
containerNode.nodeType === DOCUMENT_NODE ? (containerNode : any).documentElement : (containerNode : any);
let root;
if (!container._reactRootContainer) {
// First clear any existing content.
while (container.lastChild) {
container.removeChild(container.lastChild);
}
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
} else {
DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
Expand All @@ -197,12 +188,12 @@ var ReactDOM = {
return renderSubtreeIntoContainer(null, element, container, callback);
},

unstable_renderSubtreeIntoContainer(parentComponent : ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
unstable_renderSubtreeIntoContainer(parentComponent : ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
invariant(
parentComponent != null && ReactInstanceMap.has(parentComponent),
'parentComponent must be a valid React Component'
);
return renderSubtreeIntoContainer(parentComponent, element, container, callback);
return renderSubtreeIntoContainer(parentComponent, element, containerNode, callback);
},

unmountComponentAtNode(container : DOMContainerElement) {
Expand Down
70 changes: 24 additions & 46 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
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,59 +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({

updateContainer(containerInfo : Container, children : HostChildren<Instance | TextInstance>) : void {
containerInfo.children = flattenChildren(children);
},

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 @@ -94,17 +69,17 @@ 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;
},

commitTextUpdate(textInstance : TextInstance, oldText : string, newText : string) : void {
textInstance.text = newText;
},

appendChild(parentInstance : Instance, child : Instance | TextInstance) : void {
appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
Expand All @@ -113,7 +88,7 @@ var NoopRenderer = ReactFiberReconciler({
},

insertBefore(
parentInstance : Instance,
parentInstance : Instance | Container,
child : Instance | TextInstance,
beforeChild : Instance | TextInstance
) : void {
Expand All @@ -128,7 +103,7 @@ var NoopRenderer = ReactFiberReconciler({
parentInstance.children.splice(beforeIndex, 0, child);
},

removeChild(parentInstance : Instance, child : Instance | TextInstance) : void {
removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
const index = parentInstance.children.indexOf(child);
if (index === -1) {
throw new Error('This child does not exist.');
Expand Down Expand Up @@ -213,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 @@ -278,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
19 changes: 18 additions & 1 deletion src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,24 @@ module.exports = function<T, P, I, TI, C>(
}

function updatePortalComponent(current, workInProgress) {
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
const priorityLevel = workInProgress.pendingWorkPriority;
const nextChildren = workInProgress.pendingProps;
if (!current) {
// Portals are special because we don't append the children during mount
// but at commit. Therefore we need to track insertions which the normal
// flow doesn't do during mount. This doesn't happen at the root because
// the root always starts with a "current" with a null child.
// TODO: Consider unifying this with how the root works.
workInProgress.child = reconcileChildFibersInPlace(
workInProgress,
workInProgress.child,
nextChildren,
priorityLevel
);
markChildAsProgressed(current, workInProgress, priorityLevel);
} else {
reconcileChildren(current, workInProgress, nextChildren);
}
}

/*
Expand Down
Loading

0 comments on commit 7d2be2c

Please sign in to comment.