@samitouri / QOS-React-2 / commits / 26ea341870

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 }