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.
Sebastian Markbage committed
Nov 20, 2014 at 11:45 UTC
26ea341870469c27f12c21df3d256b83fc555d40
2 files changed
+138
-32
src/core/ReactChildReconciler.js
new
+121
@@ -0,0 +1,121 @@
1
+/**
2
+ * Copyright 2014, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactChildReconciler
10
+ * @typechecks static-only
11
+ */
12
+
13
+"use strict";
14
+
15
+var flattenChildren = require('flattenChildren');
16
+var instantiateReactComponent = require('instantiateReactComponent');
17
+var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
18
+
19
+/**
20
+ * ReactChildReconciler provides helpers for initializing or updating a set of
21
+ * children. Its output is suitable for passing it onto ReactMultiChild which
22
+ * does diffed reordering and insertion.
23
+ */
24
+var ReactChildReconciler = {
25
+
26
+ /**
27
+ * Generates a "mount image" for each of the supplied children. In the case
28
+ * of `ReactDOMComponent`, a mount image is a string of markup.
29
+ *
30
+ * @param {?object} nestedChildNodes Nested child maps.
31
+ * @return {?object} A set of child instances.
32
+ * @internal
33
+ */
34
+ instantiateChildren: function(nestedChildNodes, transaction, context) {
35
+ var children = flattenChildren(nestedChildNodes);
36
+ for (var name in children) {
37
+ if (children.hasOwnProperty(name)) {
38
+ var child = children[name];
39
+ // The rendered children must be turned into instances as they're
40
+ // mounted.
41
+ var childInstance = instantiateReactComponent(child, null);
42
+ children[name] = childInstance;
43
+ }
44
+ }
45
+ return children;
46
+ },
47
+
48
+ /**
49
+ * Updates the rendered children and returns a new set of children.
50
+ *
51
+ * @param {?object} prevChildren Previously initialized set of children.
52
+ * @param {?object} nextNestedChildNodes Nested child maps.
53
+ * @param {ReactReconcileTransaction} transaction
54
+ * @param {object} context
55
+ * @return {?object} A new set of child instances.
56
+ * @internal
57
+ */
58
+ updateChildren: function(
59
+ prevChildren,
60
+ nextNestedChildNodes,
61
+ transaction,
62
+ context) {
63
+ // We currently don't have a way to track moves here but if we use iterators
64
+ // instead of for..in we can zip the iterators and check if an item has
65
+ // moved.
66
+ // TODO: If nothing has changed, return the prevChildren object so that we
67
+ // can quickly bailout if nothing has changed.
68
+ var nextChildren = flattenChildren(nextNestedChildNodes);
69
+ if (!nextChildren && !prevChildren) {
70
+ return;
71
+ }
72
+ var name;
73
+ for (name in nextChildren) {
74
+ if (!nextChildren.hasOwnProperty(name)) {
75
+ continue;
76
+ }
77
+ var prevChild = prevChildren && prevChildren[name];
78
+ var prevElement = prevChild && prevChild._currentElement;
79
+ var nextElement = nextChildren[name];
80
+ if (shouldUpdateReactComponent(prevElement, nextElement)) {
81
+ prevChild.receiveComponent(nextElement, transaction, context);
82
+ nextChildren[name] = prevChild;
83
+ } else {
84
+ if (prevChild) {
85
+ prevChild.unmountComponent();
86
+ }
87
+ // The child must be instantiated before it's mounted.
88
+ var nextChildInstance = instantiateReactComponent(
89
+ nextElement,
90
+ null
91
+ );
92
+ nextChildren[name] = nextChildInstance;
93
+ }
94
+ }
95
+ // Unmount children that are no longer present.
96
+ for (name in prevChildren) {
97
+ if (prevChildren.hasOwnProperty(name) &&
98
+ !(nextChildren && nextChildren.hasOwnProperty(name))) {
99
+ prevChildren[name].unmountComponent();
100
+ }
101
+ }
102
+ return nextChildren;
103
+ },
104
+
105
+ /**
106
+ * Unmounts all rendered children. This should be used to clean up children
107
+ * when this component is unmounted.
108
+ *
109
+ * @param {?object} renderedChildren Previously initialized set of children.
110
+ * @internal
111
+ */
112
+ unmountChildren: function(renderedChildren) {
113
+ for (var name in renderedChildren) {
114
+ var renderedChild = renderedChildren[name];
115
+ renderedChild.unmountComponent();
116
+ }
117
+ }
118
+
119
+};
120
+
121
+module.exports = ReactChildReconciler;
src/core/ReactMultiChild.js
+17
-32
@@ -15,9 +15,7 @@
15
var ReactComponentEnvironment = require('ReactComponentEnvironment');
16
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
17
18
-var flattenChildren = require('flattenChildren');
19
-var instantiateReactComponent = require('instantiateReactComponent');
20
-var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
18
+var ReactChildReconciler = require('ReactChildReconciler');
19
20
/**
21
* Updating children of a component may trigger recursive updates. The depth is
@@ -179,25 +177,23 @@ var ReactMultiChild = {
177
* @internal
178
*/
179
mountChildren: function(nestedChildren, transaction, context) {
182
- var children = flattenChildren(nestedChildren);
180
+ var children = ReactChildReconciler.instantiateChildren(
181
+ nestedChildren, transaction, context
182
+ );
183
+ this._renderedChildren = children;
184
var mountImages = [];
185
var index = 0;
185
- this._renderedChildren = children;
186
for (var name in children) {
187
var child = children[name];
188
if (children.hasOwnProperty(name)) {
189
- // The rendered children must be turned into instances as they're
190
- // mounted.
191
- var childInstance = instantiateReactComponent(child, null);
192
- children[name] = childInstance;
189
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
190
var rootID = this._rootNodeID + name;
195
- var mountImage = childInstance.mountComponent(
191
+ var mountImage = child.mountComponent(
192
rootID,
193
transaction,
194
context
195
);
200
- childInstance._mountIndex = index;
196
+ child._mountIndex = index;
197
mountImages.push(mountImage);
198
index++;
199
}
@@ -217,6 +213,8 @@ var ReactMultiChild = {
213
try {
214
var prevChildren = this._renderedChildren;
215
// Remove any rendered children.
216
+ ReactChildReconciler.unmountChildren(prevChildren);
217
+ // TODO: The setTextContent operation should be enough
218
for (var name in prevChildren) {
219
if (prevChildren.hasOwnProperty(name)) {
220
this._unmountChildByName(prevChildren[name], name);
@@ -264,8 +262,11 @@ var ReactMultiChild = {
262
* @protected
263
*/
264
_updateChildren: function(nextNestedChildren, transaction, context) {
267
- var nextChildren = flattenChildren(nextNestedChildren);
265
var prevChildren = this._renderedChildren;
266
+ var nextChildren = ReactChildReconciler.updateChildren(
267
+ prevChildren, nextNestedChildren, transaction, context
268
+ );
269
+ this._renderedChildren = nextChildren;
270
if (!nextChildren && !prevChildren) {
271
return;
272
}
@@ -279,12 +280,10 @@ var ReactMultiChild = {
280
continue;
281
}
282
var prevChild = prevChildren && prevChildren[name];
282
- var prevElement = prevChild && prevChild._currentElement;
283
- var nextElement = nextChildren[name];
284
- if (shouldUpdateReactComponent(prevElement, nextElement)) {
283
+ var nextChild = nextChildren[name];
284
+ if (prevChild === nextChild) {
285
this.moveChild(prevChild, nextIndex, lastIndex);
286
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
287
- prevChild.receiveComponent(nextElement, transaction, context);
287
prevChild._mountIndex = nextIndex;
288
} else {
289
if (prevChild) {
@@ -293,12 +292,8 @@ var ReactMultiChild = {
292
this._unmountChildByName(prevChild, name);
293
}
294
// The child must be instantiated before it's mounted.
296
- var nextChildInstance = instantiateReactComponent(
297
- nextElement,
298
- null
299
- );
295
this._mountChildByNameAtIndex(
301
- nextChildInstance, name, nextIndex, transaction, context
296
+ nextChild, name, nextIndex, transaction, context
297
);
298
}
299
nextIndex++;
@@ -320,13 +315,7 @@ var ReactMultiChild = {
315
*/
316
unmountChildren: function() {
317
var renderedChildren = this._renderedChildren;
323
- for (var name in renderedChildren) {
324
- var renderedChild = renderedChildren[name];
325
- // TODO: When is this not true?
326
- if (renderedChild.unmountComponent) {
327
- renderedChild.unmountComponent();
328
- }
329
- }
318
+ ReactChildReconciler.unmountChildren(renderedChildren);
319
this._renderedChildren = null;
320
},
321
@@ -404,8 +393,6 @@ var ReactMultiChild = {
393
);
394
child._mountIndex = index;
395
this.createChild(child, mountImage);
407
- this._renderedChildren = this._renderedChildren || {};
408
- this._renderedChildren[name] = child;
396
},
397
398
/**
@@ -420,8 +407,6 @@ var ReactMultiChild = {
407
_unmountChildByName: function(child, name) {
408
this.removeChild(child);
409
child._mountIndex = null;
423
- child.unmountComponent();
424
- delete this._renderedChildren[name];
410
}
411
412
}