Separate Child Reconciliation Step from Diffing

This separates the reconciliation step of children into a separate module.
This is the first step towards prerendering.

The stateful instances are reconciled and put into a "rendered children"
set. Updates creates a new of these sets. These two sets are then diffed
to create insert/move/remove operations on the set.

The next step is to move the ReactChildReconciler step to before the
native DOM component. That way it's possible to rely on child
reconciliation without relying on diffing.
This commit is contained in:
Sebastian Markbage
2014-11-20 11:45:02 -08:00
parent 006bc28633
commit 26ea341870
2 changed files with 138 additions and 32 deletions

View File

@@ -0,0 +1,121 @@
/**
* Copyright 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactChildReconciler
* @typechecks static-only
*/
"use strict";
var flattenChildren = require('flattenChildren');
var instantiateReactComponent = require('instantiateReactComponent');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
/**
* ReactChildReconciler provides helpers for initializing or updating a set of
* children. Its output is suitable for passing it onto ReactMultiChild which
* does diffed reordering and insertion.
*/
var ReactChildReconciler = {
/**
* Generates a "mount image" for each of the supplied children. In the case
* of `ReactDOMComponent`, a mount image is a string of markup.
*
* @param {?object} nestedChildNodes Nested child maps.
* @return {?object} A set of child instances.
* @internal
*/
instantiateChildren: function(nestedChildNodes, transaction, context) {
var children = flattenChildren(nestedChildNodes);
for (var name in children) {
if (children.hasOwnProperty(name)) {
var child = children[name];
// The rendered children must be turned into instances as they're
// mounted.
var childInstance = instantiateReactComponent(child, null);
children[name] = childInstance;
}
}
return children;
},
/**
* Updates the rendered children and returns a new set of children.
*
* @param {?object} prevChildren Previously initialized set of children.
* @param {?object} nextNestedChildNodes Nested child maps.
* @param {ReactReconcileTransaction} transaction
* @param {object} context
* @return {?object} A new set of child instances.
* @internal
*/
updateChildren: function(
prevChildren,
nextNestedChildNodes,
transaction,
context) {
// We currently don't have a way to track moves here but if we use iterators
// instead of for..in we can zip the iterators and check if an item has
// moved.
// TODO: If nothing has changed, return the prevChildren object so that we
// can quickly bailout if nothing has changed.
var nextChildren = flattenChildren(nextNestedChildNodes);
if (!nextChildren && !prevChildren) {
return;
}
var name;
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue;
}
var prevChild = prevChildren && prevChildren[name];
var prevElement = prevChild && prevChild._currentElement;
var nextElement = nextChildren[name];
if (shouldUpdateReactComponent(prevElement, nextElement)) {
prevChild.receiveComponent(nextElement, transaction, context);
nextChildren[name] = prevChild;
} else {
if (prevChild) {
prevChild.unmountComponent();
}
// The child must be instantiated before it's mounted.
var nextChildInstance = instantiateReactComponent(
nextElement,
null
);
nextChildren[name] = nextChildInstance;
}
}
// Unmount children that are no longer present.
for (name in prevChildren) {
if (prevChildren.hasOwnProperty(name) &&
!(nextChildren && nextChildren.hasOwnProperty(name))) {
prevChildren[name].unmountComponent();
}
}
return nextChildren;
},
/**
* Unmounts all rendered children. This should be used to clean up children
* when this component is unmounted.
*
* @param {?object} renderedChildren Previously initialized set of children.
* @internal
*/
unmountChildren: function(renderedChildren) {
for (var name in renderedChildren) {
var renderedChild = renderedChildren[name];
renderedChild.unmountComponent();
}
}
};
module.exports = ReactChildReconciler;

View File

@@ -15,9 +15,7 @@
var ReactComponentEnvironment = require('ReactComponentEnvironment');
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
var flattenChildren = require('flattenChildren');
var instantiateReactComponent = require('instantiateReactComponent');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var ReactChildReconciler = require('ReactChildReconciler');
/**
* Updating children of a component may trigger recursive updates. The depth is
@@ -179,25 +177,23 @@ var ReactMultiChild = {
* @internal
*/
mountChildren: function(nestedChildren, transaction, context) {
var children = flattenChildren(nestedChildren);
var children = ReactChildReconciler.instantiateChildren(
nestedChildren, transaction, context
);
this._renderedChildren = children;
var mountImages = [];
var index = 0;
this._renderedChildren = children;
for (var name in children) {
var child = children[name];
if (children.hasOwnProperty(name)) {
// The rendered children must be turned into instances as they're
// mounted.
var childInstance = instantiateReactComponent(child, null);
children[name] = childInstance;
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
var rootID = this._rootNodeID + name;
var mountImage = childInstance.mountComponent(
var mountImage = child.mountComponent(
rootID,
transaction,
context
);
childInstance._mountIndex = index;
child._mountIndex = index;
mountImages.push(mountImage);
index++;
}
@@ -217,6 +213,8 @@ var ReactMultiChild = {
try {
var prevChildren = this._renderedChildren;
// Remove any rendered children.
ReactChildReconciler.unmountChildren(prevChildren);
// TODO: The setTextContent operation should be enough
for (var name in prevChildren) {
if (prevChildren.hasOwnProperty(name)) {
this._unmountChildByName(prevChildren[name], name);
@@ -264,8 +262,11 @@ var ReactMultiChild = {
* @protected
*/
_updateChildren: function(nextNestedChildren, transaction, context) {
var nextChildren = flattenChildren(nextNestedChildren);
var prevChildren = this._renderedChildren;
var nextChildren = ReactChildReconciler.updateChildren(
prevChildren, nextNestedChildren, transaction, context
);
this._renderedChildren = nextChildren;
if (!nextChildren && !prevChildren) {
return;
}
@@ -279,12 +280,10 @@ var ReactMultiChild = {
continue;
}
var prevChild = prevChildren && prevChildren[name];
var prevElement = prevChild && prevChild._currentElement;
var nextElement = nextChildren[name];
if (shouldUpdateReactComponent(prevElement, nextElement)) {
var nextChild = nextChildren[name];
if (prevChild === nextChild) {
this.moveChild(prevChild, nextIndex, lastIndex);
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
prevChild.receiveComponent(nextElement, transaction, context);
prevChild._mountIndex = nextIndex;
} else {
if (prevChild) {
@@ -293,12 +292,8 @@ var ReactMultiChild = {
this._unmountChildByName(prevChild, name);
}
// The child must be instantiated before it's mounted.
var nextChildInstance = instantiateReactComponent(
nextElement,
null
);
this._mountChildByNameAtIndex(
nextChildInstance, name, nextIndex, transaction, context
nextChild, name, nextIndex, transaction, context
);
}
nextIndex++;
@@ -320,13 +315,7 @@ var ReactMultiChild = {
*/
unmountChildren: function() {
var renderedChildren = this._renderedChildren;
for (var name in renderedChildren) {
var renderedChild = renderedChildren[name];
// TODO: When is this not true?
if (renderedChild.unmountComponent) {
renderedChild.unmountComponent();
}
}
ReactChildReconciler.unmountChildren(renderedChildren);
this._renderedChildren = null;
},
@@ -404,8 +393,6 @@ var ReactMultiChild = {
);
child._mountIndex = index;
this.createChild(child, mountImage);
this._renderedChildren = this._renderedChildren || {};
this._renderedChildren[name] = child;
},
/**
@@ -420,8 +407,6 @@ var ReactMultiChild = {
_unmountChildByName: function(child, name) {
this.removeChild(child);
child._mountIndex = null;
child.unmountComponent();
delete this._renderedChildren[name];
}
}