Perf: Insert nodes top-down in IE and Edge

This commit is contained in:
Ben Alpert
2015-10-22 20:58:44 -07:00
parent 4d41cf740a
commit 263ca4792e
6 changed files with 140 additions and 33 deletions
+2 -1
View File
@@ -12,6 +12,7 @@
'use strict';
var ClientReactRootIndex = require('ClientReactRootIndex');
var DOMLazyTree = require('DOMLazyTree');
var DOMProperty = require('DOMProperty');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactCurrentOwner = require('ReactCurrentOwner');
@@ -1038,7 +1039,7 @@ var ReactMount = {
while (container.lastChild) {
container.removeChild(container.lastChild);
}
container.appendChild(markup);
DOMLazyTree.insertTreeBefore(container, markup, null);
} else {
setInnerHTML(container, markup);
}
@@ -12,6 +12,7 @@
'use strict';
var DOMLazyTree = require('DOMLazyTree');
var Danger = require('Danger');
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
var ReactPerf = require('ReactPerf');
@@ -29,21 +30,27 @@ var invariant = require('invariant');
* @internal
*/
function insertChildAt(parentNode, childNode, index) {
// By exploiting arrays returning `undefined` for an undefined index, we can
// rely exclusively on `insertBefore(node, null)` instead of also using
// `appendChild(node)`. However, using `undefined` is not allowed by all
// browsers so we must replace it with `null`.
// We can rely exclusively on `insertBefore(node, null)` instead of also using
// `appendChild(node)`. (Using `undefined` is not allowed by all browsers so
// we are careful to use `null`.)
// fix render order error in safari
// IE8 will throw error when index out of list size.
var beforeChild = index >= parentNode.childNodes.length ?
null :
parentNode.childNodes.item(index);
// In Safari, .childNodes[index] can return a DOM node with id={index} so we
// use .item() instead which is immune to this bug. (See #3560.) In contrast
// to the spec, IE8 throws an error if index is larger than the list size.
var referenceNode =
index < parentNode.childNodes.length ?
parentNode.childNodes.item(index) : null;
parentNode.insertBefore(
childNode,
beforeChild
);
parentNode.insertBefore(childNode, referenceNode);
}
function insertLazyTreeChildAt(parentNode, childTree, index) {
// See above.
var referenceNode =
index < parentNode.childNodes.length ?
parentNode.childNodes.item(index) : null;
DOMLazyTree.insertTreeBefore(parentNode, childTree, referenceNode);
}
/**
@@ -99,9 +106,10 @@ var DOMChildrenOperations = {
}
}
var renderedMarkup;
// markupList is either a list of markup or just a list of elements
if (markupList.length && typeof markupList[0] === 'string') {
var isHTML = markupList.length && typeof markupList[0] === 'string';
var renderedMarkup;
if (isHTML) {
renderedMarkup = Danger.dangerouslyRenderMarkup(markupList);
} else {
renderedMarkup = markupList;
@@ -118,11 +126,19 @@ var DOMChildrenOperations = {
update = updates[k];
switch (update.type) {
case ReactMultiChildUpdateTypes.INSERT_MARKUP:
insertChildAt(
update.parentNode,
renderedMarkup[update.markupIndex],
update.toIndex
);
if (isHTML) {
insertChildAt(
update.parentNode,
renderedMarkup[update.markupIndex],
update.toIndex
);
} else {
insertLazyTreeChildAt(
update.parentNode,
renderedMarkup[update.markupIndex],
update.toIndex
);
}
break;
case ReactMultiChildUpdateTypes.MOVE_EXISTING:
insertChildAt(
@@ -0,0 +1,88 @@
/**
* Copyright 2015, 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 DOMLazyTree
*/
'use strict';
/**
* In IE (8-11) and Edge, appending nodes with no children is dramatically
* faster than appending a full subtree, so we essentially queue up the
* .appendChild calls here and apply them so each node is added to its parent
* before any children are added.
*
* In other browsers, doing so is slower or neutral compared to the other order
* (in Firefox, twice as slow) so we only do this inversion in IE.
*
* See https://github.com/spicyj/innerhtml-vs-createelement-vs-clonenode.
*/
var enableLazy = (
typeof document !== 'undefined' &&
typeof document.documentMode === 'number'
||
typeof navigator !== 'undefined' &&
typeof navigator.userAgent === 'string' &&
/\bEdge\/\d/.test(navigator.userAgent)
);
function insertTreeChildren(tree) {
if (!enableLazy) {
return;
}
var node = tree.node;
var children = tree.children;
if (children.length) {
for (var i = 0; i < children.length; i++) {
insertTreeBefore(node, children[i], null);
}
} else if (tree.html != null) {
node.innerHTML = tree.html;
}
}
function insertTreeBefore(parentNode, tree, referenceNode) {
parentNode.insertBefore(tree.node, referenceNode);
insertTreeChildren(tree);
}
function replaceChildWithTree(oldNode, newTree) {
oldNode.parentNode.replaceChild(newTree.node, oldNode);
insertTreeChildren(newTree);
}
function queueChild(parentTree, childTree) {
if (enableLazy) {
parentTree.children.push(childTree);
} else {
parentTree.node.appendChild(childTree.node);
}
}
function queueHTML(tree, html) {
if (enableLazy) {
tree.html = html;
} else {
tree.node.innerHTML = html;
}
}
function DOMLazyTree(node) {
return {
node: node,
children: [],
html: null,
};
}
DOMLazyTree.insertTreeBefore = insertTreeBefore;
DOMLazyTree.replaceChildWithTree = replaceChildWithTree;
DOMLazyTree.queueChild = queueChild;
DOMLazyTree.queueHTML = queueHTML;
module.exports = DOMLazyTree;
+4 -4
View File
@@ -12,6 +12,7 @@
'use strict';
var DOMLazyTree = require('DOMLazyTree');
var ExecutionEnvironment = require('ExecutionEnvironment');
var createNodesFromMarkup = require('createNodesFromMarkup');
@@ -172,13 +173,12 @@ var Danger = {
'server rendering. See ReactDOMServer.renderToString().'
);
var newChild;
if (typeof markup === 'string') {
newChild = createNodesFromMarkup(markup, emptyFunction)[0];
var newChild = createNodesFromMarkup(markup, emptyFunction)[0];
oldChild.parentNode.replaceChild(newChild, oldChild);
} else {
newChild = markup;
DOMLazyTree.replaceChildWithTree(oldChild, markup);
}
oldChild.parentNode.replaceChild(newChild, oldChild);
},
};
@@ -16,6 +16,7 @@
var AutoFocusUtils = require('AutoFocusUtils');
var CSSPropertyOperations = require('CSSPropertyOperations');
var DOMLazyTree = require('DOMLazyTree');
var DOMNamespaces = require('DOMNamespaces');
var DOMProperty = require('DOMProperty');
var DOMPropertyOperations = require('DOMPropertyOperations');
@@ -41,7 +42,6 @@ var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
var invariant = require('invariant');
var isEventSupported = require('isEventSupported');
var keyOf = require('keyOf');
var setInnerHTML = require('setInnerHTML');
var setTextContent = require('setTextContent');
var shallowEqual = require('shallowEqual');
var validateDOMNesting = require('validateDOMNesting');
@@ -667,8 +667,9 @@ ReactDOMComponent.Mixin = {
// Populate node cache
ReactMount.getID(el);
this._updateDOMProperties({}, props, transaction);
this._createInitialChildren(transaction, props, context, el);
mountImage = el;
var lazyTree = DOMLazyTree(el);
this._createInitialChildren(transaction, props, context, lazyTree);
mountImage = lazyTree;
} else {
var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
var tagContent = this._createContentMarkup(transaction, props, context);
@@ -814,12 +815,12 @@ ReactDOMComponent.Mixin = {
}
},
_createInitialChildren: function(transaction, props, context, el) {
_createInitialChildren: function(transaction, props, context, lazyTree) {
// Intentional use of != to avoid catching zero/false.
var innerHTML = props.dangerouslySetInnerHTML;
if (innerHTML != null) {
if (innerHTML.__html != null) {
setInnerHTML(el, innerHTML.__html);
DOMLazyTree.queueHTML(lazyTree, innerHTML.__html);
}
} else {
var contentToUse =
@@ -827,7 +828,7 @@ ReactDOMComponent.Mixin = {
var childrenToUse = contentToUse != null ? null : props.children;
if (contentToUse != null) {
// TODO: Validate that text is allowed as a child of this node
setTextContent(el, contentToUse);
setTextContent(lazyTree.node, contentToUse);
} else if (childrenToUse != null) {
var mountImages = this.mountChildren(
childrenToUse,
@@ -835,7 +836,7 @@ ReactDOMComponent.Mixin = {
context
);
for (var i = 0; i < mountImages.length; i++) {
el.appendChild(mountImages[i]);
DOMLazyTree.queueChild(lazyTree, mountImages[i]);
}
}
}
@@ -13,6 +13,7 @@
'use strict';
var DOMChildrenOperations = require('DOMChildrenOperations');
var DOMLazyTree = require('DOMLazyTree');
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactComponentBrowserEnvironment =
require('ReactComponentBrowserEnvironment');
@@ -106,7 +107,7 @@ assign(ReactDOMTextComponent.prototype, {
// Populate node cache
ReactMount.getID(el);
setTextContent(el, this._stringText);
return el;
return DOMLazyTree(el);
} else {
var escapedText = escapeTextContentForBrowser(this._stringText);