Skip to content

Commit 26ea341

Browse files
committed
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.
1 parent 006bc28 commit 26ea341

File tree

2 files changed

+138
-32
lines changed

2 files changed

+138
-32
lines changed

src/core/ReactChildReconciler.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
var ReactComponentEnvironment = require('ReactComponentEnvironment');
1616
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
1717

18-
var flattenChildren = require('flattenChildren');
19-
var instantiateReactComponent = require('instantiateReactComponent');
20-
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
18+
var ReactChildReconciler = require('ReactChildReconciler');
2119

2220
/**
2321
* Updating children of a component may trigger recursive updates. The depth is
@@ -179,25 +177,23 @@ var ReactMultiChild = {
179177
* @internal
180178
*/
181179
mountChildren: function(nestedChildren, transaction, context) {
182-
var children = flattenChildren(nestedChildren);
180+
var children = ReactChildReconciler.instantiateChildren(
181+
nestedChildren, transaction, context
182+
);
183+
this._renderedChildren = children;
183184
var mountImages = [];
184185
var index = 0;
185-
this._renderedChildren = children;
186186
for (var name in children) {
187187
var child = children[name];
188188
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;
193189
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
194190
var rootID = this._rootNodeID + name;
195-
var mountImage = childInstance.mountComponent(
191+
var mountImage = child.mountComponent(
196192
rootID,
197193
transaction,
198194
context
199195
);
200-
childInstance._mountIndex = index;
196+
child._mountIndex = index;
201197
mountImages.push(mountImage);
202198
index++;
203199
}
@@ -217,6 +213,8 @@ var ReactMultiChild = {
217213
try {
218214
var prevChildren = this._renderedChildren;
219215
// Remove any rendered children.
216+
ReactChildReconciler.unmountChildren(prevChildren);
217+
// TODO: The setTextContent operation should be enough
220218
for (var name in prevChildren) {
221219
if (prevChildren.hasOwnProperty(name)) {
222220
this._unmountChildByName(prevChildren[name], name);
@@ -264,8 +262,11 @@ var ReactMultiChild = {
264262
* @protected
265263
*/
266264
_updateChildren: function(nextNestedChildren, transaction, context) {
267-
var nextChildren = flattenChildren(nextNestedChildren);
268265
var prevChildren = this._renderedChildren;
266+
var nextChildren = ReactChildReconciler.updateChildren(
267+
prevChildren, nextNestedChildren, transaction, context
268+
);
269+
this._renderedChildren = nextChildren;
269270
if (!nextChildren && !prevChildren) {
270271
return;
271272
}
@@ -279,12 +280,10 @@ var ReactMultiChild = {
279280
continue;
280281
}
281282
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) {
285285
this.moveChild(prevChild, nextIndex, lastIndex);
286286
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
287-
prevChild.receiveComponent(nextElement, transaction, context);
288287
prevChild._mountIndex = nextIndex;
289288
} else {
290289
if (prevChild) {
@@ -293,12 +292,8 @@ var ReactMultiChild = {
293292
this._unmountChildByName(prevChild, name);
294293
}
295294
// The child must be instantiated before it's mounted.
296-
var nextChildInstance = instantiateReactComponent(
297-
nextElement,
298-
null
299-
);
300295
this._mountChildByNameAtIndex(
301-
nextChildInstance, name, nextIndex, transaction, context
296+
nextChild, name, nextIndex, transaction, context
302297
);
303298
}
304299
nextIndex++;
@@ -320,13 +315,7 @@ var ReactMultiChild = {
320315
*/
321316
unmountChildren: function() {
322317
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);
330319
this._renderedChildren = null;
331320
},
332321

@@ -404,8 +393,6 @@ var ReactMultiChild = {
404393
);
405394
child._mountIndex = index;
406395
this.createChild(child, mountImage);
407-
this._renderedChildren = this._renderedChildren || {};
408-
this._renderedChildren[name] = child;
409396
},
410397

411398
/**
@@ -420,8 +407,6 @@ var ReactMultiChild = {
420407
_unmountChildByName: function(child, name) {
421408
this.removeChild(child);
422409
child._mountIndex = null;
423-
child.unmountComponent();
424-
delete this._renderedChildren[name];
425410
}
426411

427412
}

0 commit comments

Comments
 (0)