UNPKG

14.6 kBJavaScriptView Raw
1/**
2 * Copyright 2013-present, 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 */
10
11'use strict';
12
13var _prodInvariant = require('./reactProdInvariant');
14
15var ReactComponentEnvironment = require('./ReactComponentEnvironment');
16var ReactInstanceMap = require('./ReactInstanceMap');
17var ReactInstrumentation = require('./ReactInstrumentation');
18
19var ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
20var ReactReconciler = require('./ReactReconciler');
21var ReactChildReconciler = require('./ReactChildReconciler');
22
23var emptyFunction = require('fbjs/lib/emptyFunction');
24var flattenChildren = require('./flattenChildren');
25var invariant = require('fbjs/lib/invariant');
26
27/**
28 * Make an update for markup to be rendered and inserted at a supplied index.
29 *
30 * @param {string} markup Markup that renders into an element.
31 * @param {number} toIndex Destination index.
32 * @private
33 */
34function makeInsertMarkup(markup, afterNode, toIndex) {
35 // NOTE: Null values reduce hidden classes.
36 return {
37 type: 'INSERT_MARKUP',
38 content: markup,
39 fromIndex: null,
40 fromNode: null,
41 toIndex: toIndex,
42 afterNode: afterNode
43 };
44}
45
46/**
47 * Make an update for moving an existing element to another index.
48 *
49 * @param {number} fromIndex Source index of the existing element.
50 * @param {number} toIndex Destination index of the element.
51 * @private
52 */
53function makeMove(child, afterNode, toIndex) {
54 // NOTE: Null values reduce hidden classes.
55 return {
56 type: 'MOVE_EXISTING',
57 content: null,
58 fromIndex: child._mountIndex,
59 fromNode: ReactReconciler.getHostNode(child),
60 toIndex: toIndex,
61 afterNode: afterNode
62 };
63}
64
65/**
66 * Make an update for removing an element at an index.
67 *
68 * @param {number} fromIndex Index of the element to remove.
69 * @private
70 */
71function makeRemove(child, node) {
72 // NOTE: Null values reduce hidden classes.
73 return {
74 type: 'REMOVE_NODE',
75 content: null,
76 fromIndex: child._mountIndex,
77 fromNode: node,
78 toIndex: null,
79 afterNode: null
80 };
81}
82
83/**
84 * Make an update for setting the markup of a node.
85 *
86 * @param {string} markup Markup that renders into an element.
87 * @private
88 */
89function makeSetMarkup(markup) {
90 // NOTE: Null values reduce hidden classes.
91 return {
92 type: 'SET_MARKUP',
93 content: markup,
94 fromIndex: null,
95 fromNode: null,
96 toIndex: null,
97 afterNode: null
98 };
99}
100
101/**
102 * Make an update for setting the text content.
103 *
104 * @param {string} textContent Text content to set.
105 * @private
106 */
107function makeTextContent(textContent) {
108 // NOTE: Null values reduce hidden classes.
109 return {
110 type: 'TEXT_CONTENT',
111 content: textContent,
112 fromIndex: null,
113 fromNode: null,
114 toIndex: null,
115 afterNode: null
116 };
117}
118
119/**
120 * Push an update, if any, onto the queue. Creates a new queue if none is
121 * passed and always returns the queue. Mutative.
122 */
123function enqueue(queue, update) {
124 if (update) {
125 queue = queue || [];
126 queue.push(update);
127 }
128 return queue;
129}
130
131/**
132 * Processes any enqueued updates.
133 *
134 * @private
135 */
136function processQueue(inst, updateQueue) {
137 ReactComponentEnvironment.processChildrenUpdates(inst, updateQueue);
138}
139
140var setChildrenForInstrumentation = emptyFunction;
141if (process.env.NODE_ENV !== 'production') {
142 var getDebugID = function (inst) {
143 if (!inst._debugID) {
144 // Check for ART-like instances. TODO: This is silly/gross.
145 var internal;
146 if (internal = ReactInstanceMap.get(inst)) {
147 inst = internal;
148 }
149 }
150 return inst._debugID;
151 };
152 setChildrenForInstrumentation = function (children) {
153 var debugID = getDebugID(this);
154 // TODO: React Native empty components are also multichild.
155 // This means they still get into this method but don't have _debugID.
156 if (debugID !== 0) {
157 ReactInstrumentation.debugTool.onSetChildren(debugID, children ? Object.keys(children).map(function (key) {
158 return children[key]._debugID;
159 }) : []);
160 }
161 };
162}
163
164/**
165 * ReactMultiChild are capable of reconciling multiple children.
166 *
167 * @class ReactMultiChild
168 * @internal
169 */
170var ReactMultiChild = {
171 /**
172 * Provides common functionality for components that must reconcile multiple
173 * children. This is used by `ReactDOMComponent` to mount, update, and
174 * unmount child components.
175 *
176 * @lends {ReactMultiChild.prototype}
177 */
178 Mixin: {
179 _reconcilerInstantiateChildren: function (nestedChildren, transaction, context) {
180 if (process.env.NODE_ENV !== 'production') {
181 var selfDebugID = getDebugID(this);
182 if (this._currentElement) {
183 try {
184 ReactCurrentOwner.current = this._currentElement._owner;
185 return ReactChildReconciler.instantiateChildren(nestedChildren, transaction, context, selfDebugID);
186 } finally {
187 ReactCurrentOwner.current = null;
188 }
189 }
190 }
191 return ReactChildReconciler.instantiateChildren(nestedChildren, transaction, context);
192 },
193
194 _reconcilerUpdateChildren: function (prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context) {
195 var nextChildren;
196 var selfDebugID = 0;
197 if (process.env.NODE_ENV !== 'production') {
198 selfDebugID = getDebugID(this);
199 if (this._currentElement) {
200 try {
201 ReactCurrentOwner.current = this._currentElement._owner;
202 nextChildren = flattenChildren(nextNestedChildrenElements, selfDebugID);
203 } finally {
204 ReactCurrentOwner.current = null;
205 }
206 ReactChildReconciler.updateChildren(prevChildren, nextChildren, mountImages, removedNodes, transaction, this, this._hostContainerInfo, context, selfDebugID);
207 return nextChildren;
208 }
209 }
210 nextChildren = flattenChildren(nextNestedChildrenElements, selfDebugID);
211 ReactChildReconciler.updateChildren(prevChildren, nextChildren, mountImages, removedNodes, transaction, this, this._hostContainerInfo, context, selfDebugID);
212 return nextChildren;
213 },
214
215 /**
216 * Generates a "mount image" for each of the supplied children. In the case
217 * of `ReactDOMComponent`, a mount image is a string of markup.
218 *
219 * @param {?object} nestedChildren Nested child maps.
220 * @return {array} An array of mounted representations.
221 * @internal
222 */
223 mountChildren: function (nestedChildren, transaction, context) {
224 var children = this._reconcilerInstantiateChildren(nestedChildren, transaction, context);
225 this._renderedChildren = children;
226
227 var mountImages = [];
228 var index = 0;
229 for (var name in children) {
230 if (children.hasOwnProperty(name)) {
231 var child = children[name];
232 var selfDebugID = 0;
233 if (process.env.NODE_ENV !== 'production') {
234 selfDebugID = getDebugID(this);
235 }
236 var mountImage = ReactReconciler.mountComponent(child, transaction, this, this._hostContainerInfo, context, selfDebugID);
237 child._mountIndex = index++;
238 mountImages.push(mountImage);
239 }
240 }
241
242 if (process.env.NODE_ENV !== 'production') {
243 setChildrenForInstrumentation.call(this, children);
244 }
245
246 return mountImages;
247 },
248
249 /**
250 * Replaces any rendered children with a text content string.
251 *
252 * @param {string} nextContent String of content.
253 * @internal
254 */
255 updateTextContent: function (nextContent) {
256 var prevChildren = this._renderedChildren;
257 // Remove any rendered children.
258 ReactChildReconciler.unmountChildren(prevChildren, false);
259 for (var name in prevChildren) {
260 if (prevChildren.hasOwnProperty(name)) {
261 !false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'updateTextContent called on non-empty component.') : _prodInvariant('118') : void 0;
262 }
263 }
264 // Set new text content.
265 var updates = [makeTextContent(nextContent)];
266 processQueue(this, updates);
267 },
268
269 /**
270 * Replaces any rendered children with a markup string.
271 *
272 * @param {string} nextMarkup String of markup.
273 * @internal
274 */
275 updateMarkup: function (nextMarkup) {
276 var prevChildren = this._renderedChildren;
277 // Remove any rendered children.
278 ReactChildReconciler.unmountChildren(prevChildren, false);
279 for (var name in prevChildren) {
280 if (prevChildren.hasOwnProperty(name)) {
281 !false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'updateTextContent called on non-empty component.') : _prodInvariant('118') : void 0;
282 }
283 }
284 var updates = [makeSetMarkup(nextMarkup)];
285 processQueue(this, updates);
286 },
287
288 /**
289 * Updates the rendered children with new children.
290 *
291 * @param {?object} nextNestedChildrenElements Nested child element maps.
292 * @param {ReactReconcileTransaction} transaction
293 * @internal
294 */
295 updateChildren: function (nextNestedChildrenElements, transaction, context) {
296 // Hook used by React ART
297 this._updateChildren(nextNestedChildrenElements, transaction, context);
298 },
299
300 /**
301 * @param {?object} nextNestedChildrenElements Nested child element maps.
302 * @param {ReactReconcileTransaction} transaction
303 * @final
304 * @protected
305 */
306 _updateChildren: function (nextNestedChildrenElements, transaction, context) {
307 var prevChildren = this._renderedChildren;
308 var removedNodes = {};
309 var mountImages = [];
310 var nextChildren = this._reconcilerUpdateChildren(prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context);
311 if (!nextChildren && !prevChildren) {
312 return;
313 }
314 var updates = null;
315 var name;
316 // `nextIndex` will increment for each child in `nextChildren`, but
317 // `lastIndex` will be the last index visited in `prevChildren`.
318 var nextIndex = 0;
319 var lastIndex = 0;
320 // `nextMountIndex` will increment for each newly mounted child.
321 var nextMountIndex = 0;
322 var lastPlacedNode = null;
323 for (name in nextChildren) {
324 if (!nextChildren.hasOwnProperty(name)) {
325 continue;
326 }
327 var prevChild = prevChildren && prevChildren[name];
328 var nextChild = nextChildren[name];
329 if (prevChild === nextChild) {
330 updates = enqueue(updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex));
331 lastIndex = Math.max(prevChild._mountIndex, lastIndex);
332 prevChild._mountIndex = nextIndex;
333 } else {
334 if (prevChild) {
335 // Update `lastIndex` before `_mountIndex` gets unset by unmounting.
336 lastIndex = Math.max(prevChild._mountIndex, lastIndex);
337 // The `removedNodes` loop below will actually remove the child.
338 }
339 // The child must be instantiated before it's mounted.
340 updates = enqueue(updates, this._mountChildAtIndex(nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context));
341 nextMountIndex++;
342 }
343 nextIndex++;
344 lastPlacedNode = ReactReconciler.getHostNode(nextChild);
345 }
346 // Remove children that are no longer present.
347 for (name in removedNodes) {
348 if (removedNodes.hasOwnProperty(name)) {
349 updates = enqueue(updates, this._unmountChild(prevChildren[name], removedNodes[name]));
350 }
351 }
352 if (updates) {
353 processQueue(this, updates);
354 }
355 this._renderedChildren = nextChildren;
356
357 if (process.env.NODE_ENV !== 'production') {
358 setChildrenForInstrumentation.call(this, nextChildren);
359 }
360 },
361
362 /**
363 * Unmounts all rendered children. This should be used to clean up children
364 * when this component is unmounted. It does not actually perform any
365 * backend operations.
366 *
367 * @internal
368 */
369 unmountChildren: function (safely) {
370 var renderedChildren = this._renderedChildren;
371 ReactChildReconciler.unmountChildren(renderedChildren, safely);
372 this._renderedChildren = null;
373 },
374
375 /**
376 * Moves a child component to the supplied index.
377 *
378 * @param {ReactComponent} child Component to move.
379 * @param {number} toIndex Destination index of the element.
380 * @param {number} lastIndex Last index visited of the siblings of `child`.
381 * @protected
382 */
383 moveChild: function (child, afterNode, toIndex, lastIndex) {
384 // If the index of `child` is less than `lastIndex`, then it needs to
385 // be moved. Otherwise, we do not need to move it because a child will be
386 // inserted or moved before `child`.
387 if (child._mountIndex < lastIndex) {
388 return makeMove(child, afterNode, toIndex);
389 }
390 },
391
392 /**
393 * Creates a child component.
394 *
395 * @param {ReactComponent} child Component to create.
396 * @param {string} mountImage Markup to insert.
397 * @protected
398 */
399 createChild: function (child, afterNode, mountImage) {
400 return makeInsertMarkup(mountImage, afterNode, child._mountIndex);
401 },
402
403 /**
404 * Removes a child component.
405 *
406 * @param {ReactComponent} child Child to remove.
407 * @protected
408 */
409 removeChild: function (child, node) {
410 return makeRemove(child, node);
411 },
412
413 /**
414 * Mounts a child with the supplied name.
415 *
416 * NOTE: This is part of `updateChildren` and is here for readability.
417 *
418 * @param {ReactComponent} child Component to mount.
419 * @param {string} name Name of the child.
420 * @param {number} index Index at which to insert the child.
421 * @param {ReactReconcileTransaction} transaction
422 * @private
423 */
424 _mountChildAtIndex: function (child, mountImage, afterNode, index, transaction, context) {
425 child._mountIndex = index;
426 return this.createChild(child, afterNode, mountImage);
427 },
428
429 /**
430 * Unmounts a rendered child.
431 *
432 * NOTE: This is part of `updateChildren` and is here for readability.
433 *
434 * @param {ReactComponent} child Component to unmount.
435 * @private
436 */
437 _unmountChild: function (child, node) {
438 var update = this.removeChild(child, node);
439 child._mountIndex = null;
440 return update;
441 }
442 }
443};
444
445module.exports = ReactMultiChild;
\No newline at end of file