Skip to content

Commit 0e8db6b

Browse files
committed
Merge pull request facebook#5782 from spicyj/img
Move nodes around by reference instead of by index
2 parents 067547c + eb00290 commit 0e8db6b

11 files changed

+133
-154
lines changed

src/renderers/dom/client/utils/DOMChildrenOperations.js

+16-86
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ var ReactPerf = require('ReactPerf');
1818

1919
var setInnerHTML = require('setInnerHTML');
2020
var setTextContent = require('setTextContent');
21-
var invariant = require('invariant');
21+
22+
function getNodeAfter(parentNode, node) {
23+
return node ? node.nextSibling : parentNode.firstChild;
24+
}
2225

2326
/**
2427
* Inserts `childNode` as a child of `parentNode` at the `index`.
@@ -28,27 +31,14 @@ var invariant = require('invariant');
2831
* @param {number} index Index at which to insert the child.
2932
* @internal
3033
*/
31-
function insertChildAt(parentNode, childNode, index) {
32-
// We can rely exclusively on `insertBefore(node, null)` instead of also using
34+
function insertChildAt(parentNode, childNode, referenceNode) {
35+
// We rely exclusively on `insertBefore(node, null)` instead of also using
3336
// `appendChild(node)`. (Using `undefined` is not allowed by all browsers so
3437
// we are careful to use `null`.)
35-
36-
// In Safari, .childNodes[index] can return a DOM node with id={index} so we
37-
// use .item() instead which is immune to this bug. (See #3560.) In contrast
38-
// to the spec, IE8 throws an error if index is larger than the list size.
39-
var referenceNode =
40-
index < parentNode.childNodes.length ?
41-
parentNode.childNodes.item(index) : null;
42-
4338
parentNode.insertBefore(childNode, referenceNode);
4439
}
4540

46-
function insertLazyTreeChildAt(parentNode, childTree, index) {
47-
// See above.
48-
var referenceNode =
49-
index < parentNode.childNodes.length ?
50-
parentNode.childNodes.item(index) : null;
51-
41+
function insertLazyTreeChildAt(parentNode, childTree, referenceNode) {
5242
DOMLazyTree.insertTreeBefore(parentNode, childTree, referenceNode);
5343
}
5444

@@ -69,81 +59,21 @@ var DOMChildrenOperations = {
6959
* @internal
7060
*/
7161
processUpdates: function(parentNode, updates) {
72-
var update;
73-
// Mapping from parent IDs to initial child orderings.
74-
var initialChildren = null;
75-
// List of children that will be moved or removed.
76-
var updatedChildren = null;
77-
78-
var markupList = null;
79-
80-
for (var i = 0; i < updates.length; i++) {
81-
update = updates[i];
82-
if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING ||
83-
update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) {
84-
var updatedIndex = update.fromIndex;
85-
var updatedChild = parentNode.childNodes[updatedIndex];
86-
87-
invariant(
88-
updatedChild,
89-
'processUpdates(): Unable to find child %s of element %s. This ' +
90-
'probably means the DOM was unexpectedly mutated (e.g., by the ' +
91-
'browser), usually due to forgetting a <tbody> when using tables, ' +
92-
'nesting tags like <form>, <p>, or <a>, or using non-SVG elements ' +
93-
'in an <svg> parent.',
94-
updatedIndex,
95-
parentNode,
96-
);
97-
98-
initialChildren = initialChildren || {};
99-
initialChildren[updatedIndex] = updatedChild;
100-
101-
updatedChildren = updatedChildren || [];
102-
updatedChildren.push(updatedChild);
103-
} else if (update.type === ReactMultiChildUpdateTypes.INSERT_MARKUP) {
104-
// Replace each HTML string with an index into the markup list
105-
if (typeof update.content === 'string') {
106-
markupList = markupList || [];
107-
update.content = markupList.push(update.markup);
108-
}
109-
}
110-
}
111-
112-
var renderedMarkup;
113-
if (markupList) {
114-
renderedMarkup = Danger.dangerouslyRenderMarkup(markupList);
115-
}
116-
117-
// Remove updated children first so that `toIndex` is consistent.
118-
if (updatedChildren) {
119-
for (var j = 0; j < updatedChildren.length; j++) {
120-
parentNode.removeChild(updatedChildren[j]);
121-
}
122-
}
123-
12462
for (var k = 0; k < updates.length; k++) {
125-
update = updates[k];
63+
var update = updates[k];
12664
switch (update.type) {
12765
case ReactMultiChildUpdateTypes.INSERT_MARKUP:
128-
if (renderedMarkup) {
129-
insertChildAt(
130-
parentNode,
131-
renderedMarkup[update.content],
132-
update.toIndex
133-
);
134-
} else {
135-
insertLazyTreeChildAt(
136-
parentNode,
137-
update.content,
138-
update.toIndex
139-
);
140-
}
66+
insertLazyTreeChildAt(
67+
parentNode,
68+
update.content,
69+
getNodeAfter(parentNode, update.afterNode)
70+
);
14171
break;
14272
case ReactMultiChildUpdateTypes.MOVE_EXISTING:
14373
insertChildAt(
14474
parentNode,
145-
initialChildren[update.fromIndex],
146-
update.toIndex
75+
update.fromNode,
76+
getNodeAfter(parentNode, update.afterNode)
14777
);
14878
break;
14979
case ReactMultiChildUpdateTypes.SET_MARKUP:
@@ -159,7 +89,7 @@ var DOMChildrenOperations = {
15989
);
16090
break;
16191
case ReactMultiChildUpdateTypes.REMOVE_NODE:
162-
// Already removed by the for-loop above.
92+
parentNode.removeChild(update.fromNode);
16393
break;
16494
}
16595
}

src/renderers/dom/shared/ReactDOMComponent.js

+5-12
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,10 @@ ReactDOMComponent.Mixin = {
10091009
}
10101010
},
10111011

1012+
getNativeNode: function() {
1013+
return getNode(this);
1014+
},
1015+
10121016
/**
10131017
* Destroys all event registrations for this instance. Does not remove from
10141018
* the DOM. That must be done by the parent.
@@ -1053,15 +1057,13 @@ ReactDOMComponent.Mixin = {
10531057
break;
10541058
}
10551059

1056-
var nativeNode = getNode(this);
10571060
this.unmountChildren();
10581061
ReactDOMComponentTree.uncacheNode(this);
10591062
EventPluginHub.deleteAllListeners(this);
10601063
ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
10611064
this._rootNodeID = null;
10621065
this._domID = null;
10631066
this._wrapperState = null;
1064-
return nativeNode;
10651067
},
10661068

10671069
getPublicInstance: function() {
@@ -1078,16 +1080,7 @@ ReactPerf.measureMethods(ReactDOMComponent.Mixin, 'ReactDOMComponent', {
10781080
assign(
10791081
ReactDOMComponent.prototype,
10801082
ReactDOMComponent.Mixin,
1081-
ReactMultiChild.Mixin,
1082-
{
1083-
prepareToManageChildren: function() {
1084-
// Before we add, remove, or reorder the children of a node, make sure
1085-
// we have references to all of its children so we don't lose them, even
1086-
// if nefarious browser plugins add extra nodes to our tree. This could be
1087-
// called once per child so it should be fast.
1088-
ReactDOMComponentTree.precacheChildNodes(this, getNode(this));
1089-
},
1090-
}
1083+
ReactMultiChild.Mixin
10911084
);
10921085

10931086
module.exports = ReactDOMComponent;

src/renderers/dom/shared/ReactDOMEmptyComponent.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@ assign(ReactDOMEmptyComponent.prototype, {
5757
},
5858
receiveComponent: function() {
5959
},
60+
getNativeNode: function() {
61+
return ReactDOMComponentTree.getNodeFromInstance(this);
62+
},
6063
unmountComponent: function() {
61-
var node = ReactDOMComponentTree.getNodeFromInstance(this);
6264
ReactDOMComponentTree.uncacheNode(this);
63-
return node;
6465
},
6566
});
6667

src/renderers/dom/shared/ReactDOMTextComponent.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,13 @@ assign(ReactDOMTextComponent.prototype, {
137137
}
138138
}
139139
},
140+
141+
getNativeNode: function() {
142+
return getNode(this);
143+
},
144+
140145
unmountComponent: function() {
141-
var node = getNode(this);
142146
ReactDOMComponentTree.uncacheNode(this);
143-
return node;
144147
},
145148

146149
});

src/renderers/shared/reconciler/ReactChildReconciler.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ var ReactChildReconciler = {
7171
updateChildren: function(
7272
prevChildren,
7373
nextChildren,
74+
removedNodes,
7475
transaction,
7576
context) {
7677
// We currently don't have a way to track moves here but if we use iterators
@@ -79,14 +80,15 @@ var ReactChildReconciler = {
7980
// TODO: If nothing has changed, return the prevChildren object so that we
8081
// can quickly bailout if nothing has changed.
8182
if (!nextChildren && !prevChildren) {
82-
return null;
83+
return;
8384
}
8485
var name;
86+
var prevChild;
8587
for (name in nextChildren) {
8688
if (!nextChildren.hasOwnProperty(name)) {
8789
continue;
8890
}
89-
var prevChild = prevChildren && prevChildren[name];
91+
prevChild = prevChildren && prevChildren[name];
9092
var prevElement = prevChild && prevChild._currentElement;
9193
var nextElement = nextChildren[name];
9294
if (prevChild != null &&
@@ -97,7 +99,8 @@ var ReactChildReconciler = {
9799
nextChildren[name] = prevChild;
98100
} else {
99101
if (prevChild) {
100-
ReactReconciler.unmountComponent(prevChild, name);
102+
removedNodes[name] = ReactReconciler.getNativeNode(prevChild);
103+
ReactReconciler.unmountComponent(prevChild);
101104
}
102105
// The child must be instantiated before it's mounted.
103106
var nextChildInstance = instantiateReactComponent(nextElement);
@@ -108,10 +111,11 @@ var ReactChildReconciler = {
108111
for (name in prevChildren) {
109112
if (prevChildren.hasOwnProperty(name) &&
110113
!(nextChildren && nextChildren.hasOwnProperty(name))) {
111-
ReactReconciler.unmountComponent(prevChildren[name]);
114+
prevChild = prevChildren[name];
115+
removedNodes[name] = ReactReconciler.getNativeNode(prevChild);
116+
ReactReconciler.unmountComponent(prevChild);
112117
}
113118
}
114-
return nextChildren;
115119
},
116120

117121
/**

src/renderers/shared/reconciler/ReactCompositeComponent.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,10 @@ var ReactCompositeComponentMixin = {
370370
return markup;
371371
},
372372

373+
getNativeNode: function() {
374+
return ReactReconciler.getNativeNode(this._renderedComponent);
375+
},
376+
373377
/**
374378
* Releases any resources allocated by `mountComponent`.
375379
*
@@ -384,7 +388,7 @@ var ReactCompositeComponentMixin = {
384388
}
385389

386390
if (this._renderedComponent) {
387-
var unmountedNativeNode = ReactReconciler.unmountComponent(this._renderedComponent);
391+
ReactReconciler.unmountComponent(this._renderedComponent);
388392
this._renderedNodeType = null;
389393
this._renderedComponent = null;
390394
this._instance = null;
@@ -415,7 +419,6 @@ var ReactCompositeComponentMixin = {
415419
// TODO: inst.props = null;
416420
// TODO: inst.state = null;
417421
// TODO: inst.context = null;
418-
return unmountedNativeNode;
419422
},
420423

421424
/**
@@ -803,7 +806,15 @@ var ReactCompositeComponentMixin = {
803806
this._processChildContext(context)
804807
);
805808
} else {
806-
var oldNativeNode = ReactReconciler.unmountComponent(prevComponentInstance);
809+
// TODO: This is currently necessary due to the unfortunate caching
810+
// that ReactMount does which makes it exceedingly difficult to unmount
811+
// a set of siblings without accidentally repopulating the node cache (see
812+
// #5151). Once ReactMount no longer stores the nodes by ID, this method
813+
// can go away.
814+
var oldNativeNode = ReactReconciler.getNativeNode(prevComponentInstance);
815+
816+
ReactReconciler.unmountComponent(prevComponentInstance);
817+
807818
this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement);
808819
this._renderedComponent = this._instantiateReactComponent(
809820
nextRenderedElement

0 commit comments

Comments
 (0)