Skip to content

Coalesce adjacent strings into one text node #742

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions src/core/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ var ReactComponent = require('ReactComponent');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactMultiChild = require('ReactMultiChild');
var ReactPerf = require('ReactPerf');
var ReactTextComponent = require('ReactTextComponent');

var escapeTextForBrowser = require('escapeTextForBrowser');
var flattenChildren = require('flattenChildren');
var invariant = require('invariant');
var keyOf = require('keyOf');
var merge = require('merge');
Expand Down Expand Up @@ -61,6 +63,35 @@ function assertValidProps(props) {
);
}

/**
* Given a flattened child map with exactly one text component, return its
* text. Otherwise, return null.
*
* @param {?object} children Flattened child map
* @return {?string}
*/
function singleTextChildContent(children) {
var firstChild = null;

for (var key in children) {
if (!children.hasOwnProperty(key)) {
continue;
}
if (firstChild != null) {
// More than one child
return null;
} else {
firstChild = children[key];
}
}

if (firstChild instanceof ReactTextComponent) {
return firstChild.props.text;
} else {
return null;
}
}

/**
* @constructor ReactDOMComponent
* @extends ReactComponent
Expand Down Expand Up @@ -162,14 +193,13 @@ ReactDOMComponent.Mixin = {
return innerHTML.__html;
}
} else {
var contentToUse =
CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
var childrenToUse = contentToUse != null ? null : this.props.children;
var children = flattenChildren(this.props.children);
var contentToUse = singleTextChildContent(children);
if (contentToUse != null) {
return escapeTextForBrowser(contentToUse);
} else if (childrenToUse != null) {
} else if (children) {
var mountImages = this.mountChildren(
childrenToUse,
children,
transaction
);
return mountImages.join('');
Expand Down Expand Up @@ -315,10 +345,11 @@ ReactDOMComponent.Mixin = {
_updateDOMChildren: function(lastProps, transaction) {
var nextProps = this.props;

var lastContent =
CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null;
var nextContent =
CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
var lastChildren = this._renderedChildren;
var nextChildren = flattenChildren(nextProps.children);

var lastContent = singleTextChildContent(lastChildren);
var nextContent = singleTextChildContent(nextChildren);

var lastHtml =
lastProps.dangerouslySetInnerHTML &&
Expand All @@ -328,14 +359,14 @@ ReactDOMComponent.Mixin = {
nextProps.dangerouslySetInnerHTML.__html;

// Note the use of `!=` which checks for null or undefined.
var lastChildren = lastContent != null ? null : lastProps.children;
var nextChildren = nextContent != null ? null : nextProps.children;
var useLastChildren = lastContent == null && lastProps.children != null;
var useNextChildren = nextContent == null && nextProps.children != null;

// If we're switching from children to content/html or vice versa, remove
// the old content
var lastHasContentOrHtml = lastContent != null || lastHtml != null;
var nextHasContentOrHtml = nextContent != null || nextHtml != null;
if (lastChildren != null && nextChildren == null) {
if (useLastChildren && !useNextChildren) {
this.updateChildren(null, transaction);
} else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
this.updateTextContent('');
Expand All @@ -352,7 +383,7 @@ ReactDOMComponent.Mixin = {
nextHtml
);
}
} else if (nextChildren != null) {
} else if (useNextChildren) {
this.updateChildren(nextChildren, transaction);
}
},
Expand Down
39 changes: 16 additions & 23 deletions src/core/ReactMultiChild.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
var ReactComponent = require('ReactComponent');
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');

var flattenChildren = require('flattenChildren');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');

/**
Expand Down Expand Up @@ -180,18 +179,17 @@ var ReactMultiChild = {
* 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} nestedChildren Nested child maps.
* @param {?object} children Flattened child map
* @return {array} An array of mounted representations.
* @internal
*/
mountChildren: function(nestedChildren, transaction) {
var children = flattenChildren(nestedChildren);
mountChildren: function(children, transaction) {
var mountImages = [];
var index = 0;
this._renderedChildren = children;
for (var name in children) {
var child = children[name];
if (children.hasOwnProperty(name) && child) {
if (children.hasOwnProperty(name)) {
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
var rootID = this._rootNodeID + '.' + name;
var mountImage = child.mountComponent(
Expand Down Expand Up @@ -220,8 +218,7 @@ var ReactMultiChild = {
var prevChildren = this._renderedChildren;
// Remove any rendered children.
for (var name in prevChildren) {
if (prevChildren.hasOwnProperty(name) &&
prevChildren[name]) {
if (prevChildren.hasOwnProperty(name)) {
this._unmountChildByName(prevChildren[name], name);
}
}
Expand All @@ -239,14 +236,14 @@ var ReactMultiChild = {
/**
* Updates the rendered children with new children.
*
* @param {?object} nextNestedChildren Nested child maps.
* @param {?object} nextChildren Flattened child map
* @param {ReactReconcileTransaction} transaction
* @internal
*/
updateChildren: function(nextNestedChildren, transaction) {
updateChildren: function(nextChildren, transaction) {
updateDepth++;
try {
this._updateChildren(nextNestedChildren, transaction);
this._updateChildren(nextChildren, transaction);
} catch (error) {
updateDepth--;
updateDepth || clearQueue();
Expand All @@ -260,13 +257,12 @@ var ReactMultiChild = {
* Improve performance by isolating this hot code path from the try/catch
* block in `updateChildren`.
*
* @param {?object} nextNestedChildren Nested child maps.
* @param {?object} nextChildren Flattened child map
* @param {ReactReconcileTransaction} transaction
* @final
* @protected
*/
_updateChildren: function(nextNestedChildren, transaction) {
var nextChildren = flattenChildren(nextNestedChildren);
_updateChildren: function(nextChildren, transaction) {
var prevChildren = this._renderedChildren;
if (!nextChildren && !prevChildren) {
return;
Expand All @@ -293,20 +289,15 @@ var ReactMultiChild = {
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
this._unmountChildByName(prevChild, name);
}
if (nextChild) {
this._mountChildByNameAtIndex(
nextChild, name, nextIndex, transaction
);
}
}
if (nextChild) {
nextIndex++;
this._mountChildByNameAtIndex(
nextChild, name, nextIndex, transaction
);
}
nextIndex++;
}
// Remove children that are no longer present.
for (name in prevChildren) {
if (prevChildren.hasOwnProperty(name) &&
prevChildren[name] &&
!(nextChildren && nextChildren[name])) {
this._unmountChildByName(prevChildren[name], name);
}
Expand All @@ -323,7 +314,8 @@ var ReactMultiChild = {
var renderedChildren = this._renderedChildren;
for (var name in renderedChildren) {
var renderedChild = renderedChildren[name];
if (renderedChild && renderedChild.unmountComponent) {
// TODO: When is this not true?
if (renderedChild.unmountComponent) {
renderedChild.unmountComponent();
}
}
Expand Down Expand Up @@ -413,6 +405,7 @@ var ReactMultiChild = {
* @private
*/
_unmountChildByName: function(child, name) {
// TODO: When is this not true?
if (ReactComponent.isValidComponent(child)) {
this.removeChild(child);
child._mountImage = null;
Expand Down
Loading