Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81152 views
1
/**
2
* Copyright 2013-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 ReactMultiChild
10
* @typechecks static-only
11
*/
12
13
"use strict";
14
15
var ReactComponent = require('ReactComponent');
16
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
17
18
var flattenChildren = require('flattenChildren');
19
var instantiateReactComponent = require('instantiateReactComponent');
20
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
21
22
/**
23
* Updating children of a component may trigger recursive updates. The depth is
24
* used to batch recursive updates to render markup more efficiently.
25
*
26
* @type {number}
27
* @private
28
*/
29
var updateDepth = 0;
30
31
/**
32
* Queue of update configuration objects.
33
*
34
* Each object has a `type` property that is in `ReactMultiChildUpdateTypes`.
35
*
36
* @type {array<object>}
37
* @private
38
*/
39
var updateQueue = [];
40
41
/**
42
* Queue of markup to be rendered.
43
*
44
* @type {array<string>}
45
* @private
46
*/
47
var markupQueue = [];
48
49
/**
50
* Enqueues markup to be rendered and inserted at a supplied index.
51
*
52
* @param {string} parentID ID of the parent component.
53
* @param {string} markup Markup that renders into an element.
54
* @param {number} toIndex Destination index.
55
* @private
56
*/
57
function enqueueMarkup(parentID, markup, toIndex) {
58
// NOTE: Null values reduce hidden classes.
59
updateQueue.push({
60
parentID: parentID,
61
parentNode: null,
62
type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
63
markupIndex: markupQueue.push(markup) - 1,
64
textContent: null,
65
fromIndex: null,
66
toIndex: toIndex
67
});
68
}
69
70
/**
71
* Enqueues moving an existing element to another index.
72
*
73
* @param {string} parentID ID of the parent component.
74
* @param {number} fromIndex Source index of the existing element.
75
* @param {number} toIndex Destination index of the element.
76
* @private
77
*/
78
function enqueueMove(parentID, fromIndex, toIndex) {
79
// NOTE: Null values reduce hidden classes.
80
updateQueue.push({
81
parentID: parentID,
82
parentNode: null,
83
type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
84
markupIndex: null,
85
textContent: null,
86
fromIndex: fromIndex,
87
toIndex: toIndex
88
});
89
}
90
91
/**
92
* Enqueues removing an element at an index.
93
*
94
* @param {string} parentID ID of the parent component.
95
* @param {number} fromIndex Index of the element to remove.
96
* @private
97
*/
98
function enqueueRemove(parentID, fromIndex) {
99
// NOTE: Null values reduce hidden classes.
100
updateQueue.push({
101
parentID: parentID,
102
parentNode: null,
103
type: ReactMultiChildUpdateTypes.REMOVE_NODE,
104
markupIndex: null,
105
textContent: null,
106
fromIndex: fromIndex,
107
toIndex: null
108
});
109
}
110
111
/**
112
* Enqueues setting the text content.
113
*
114
* @param {string} parentID ID of the parent component.
115
* @param {string} textContent Text content to set.
116
* @private
117
*/
118
function enqueueTextContent(parentID, textContent) {
119
// NOTE: Null values reduce hidden classes.
120
updateQueue.push({
121
parentID: parentID,
122
parentNode: null,
123
type: ReactMultiChildUpdateTypes.TEXT_CONTENT,
124
markupIndex: null,
125
textContent: textContent,
126
fromIndex: null,
127
toIndex: null
128
});
129
}
130
131
/**
132
* Processes any enqueued updates.
133
*
134
* @private
135
*/
136
function processQueue() {
137
if (updateQueue.length) {
138
ReactComponent.BackendIDOperations.dangerouslyProcessChildrenUpdates(
139
updateQueue,
140
markupQueue
141
);
142
clearQueue();
143
}
144
}
145
146
/**
147
* Clears any enqueued updates.
148
*
149
* @private
150
*/
151
function clearQueue() {
152
updateQueue.length = 0;
153
markupQueue.length = 0;
154
}
155
156
/**
157
* ReactMultiChild are capable of reconciling multiple children.
158
*
159
* @class ReactMultiChild
160
* @internal
161
*/
162
var ReactMultiChild = {
163
164
/**
165
* Provides common functionality for components that must reconcile multiple
166
* children. This is used by `ReactDOMComponent` to mount, update, and
167
* unmount child components.
168
*
169
* @lends {ReactMultiChild.prototype}
170
*/
171
Mixin: {
172
173
/**
174
* Generates a "mount image" for each of the supplied children. In the case
175
* of `ReactDOMComponent`, a mount image is a string of markup.
176
*
177
* @param {?object} nestedChildren Nested child maps.
178
* @return {array} An array of mounted representations.
179
* @internal
180
*/
181
mountChildren: function(nestedChildren, transaction) {
182
var children = flattenChildren(nestedChildren);
183
var mountImages = [];
184
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;
193
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
194
var rootID = this._rootNodeID + name;
195
var mountImage = childInstance.mountComponent(
196
rootID,
197
transaction,
198
this._mountDepth + 1
199
);
200
childInstance._mountIndex = index;
201
mountImages.push(mountImage);
202
index++;
203
}
204
}
205
return mountImages;
206
},
207
208
/**
209
* Replaces any rendered children with a text content string.
210
*
211
* @param {string} nextContent String of content.
212
* @internal
213
*/
214
updateTextContent: function(nextContent) {
215
updateDepth++;
216
var errorThrown = true;
217
try {
218
var prevChildren = this._renderedChildren;
219
// Remove any rendered children.
220
for (var name in prevChildren) {
221
if (prevChildren.hasOwnProperty(name)) {
222
this._unmountChildByName(prevChildren[name], name);
223
}
224
}
225
// Set new text content.
226
this.setTextContent(nextContent);
227
errorThrown = false;
228
} finally {
229
updateDepth--;
230
if (!updateDepth) {
231
errorThrown ? clearQueue() : processQueue();
232
}
233
}
234
},
235
236
/**
237
* Updates the rendered children with new children.
238
*
239
* @param {?object} nextNestedChildren Nested child maps.
240
* @param {ReactReconcileTransaction} transaction
241
* @internal
242
*/
243
updateChildren: function(nextNestedChildren, transaction) {
244
updateDepth++;
245
var errorThrown = true;
246
try {
247
this._updateChildren(nextNestedChildren, transaction);
248
errorThrown = false;
249
} finally {
250
updateDepth--;
251
if (!updateDepth) {
252
errorThrown ? clearQueue() : processQueue();
253
}
254
}
255
},
256
257
/**
258
* Improve performance by isolating this hot code path from the try/catch
259
* block in `updateChildren`.
260
*
261
* @param {?object} nextNestedChildren Nested child maps.
262
* @param {ReactReconcileTransaction} transaction
263
* @final
264
* @protected
265
*/
266
_updateChildren: function(nextNestedChildren, transaction) {
267
var nextChildren = flattenChildren(nextNestedChildren);
268
var prevChildren = this._renderedChildren;
269
if (!nextChildren && !prevChildren) {
270
return;
271
}
272
var name;
273
// `nextIndex` will increment for each child in `nextChildren`, but
274
// `lastIndex` will be the last index visited in `prevChildren`.
275
var lastIndex = 0;
276
var nextIndex = 0;
277
for (name in nextChildren) {
278
if (!nextChildren.hasOwnProperty(name)) {
279
continue;
280
}
281
var prevChild = prevChildren && prevChildren[name];
282
var prevElement = prevChild && prevChild._currentElement;
283
var nextElement = nextChildren[name];
284
if (shouldUpdateReactComponent(prevElement, nextElement)) {
285
this.moveChild(prevChild, nextIndex, lastIndex);
286
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
287
prevChild.receiveComponent(nextElement, transaction);
288
prevChild._mountIndex = nextIndex;
289
} else {
290
if (prevChild) {
291
// Update `lastIndex` before `_mountIndex` gets unset by unmounting.
292
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
293
this._unmountChildByName(prevChild, name);
294
}
295
// The child must be instantiated before it's mounted.
296
var nextChildInstance = instantiateReactComponent(
297
nextElement,
298
null
299
);
300
this._mountChildByNameAtIndex(
301
nextChildInstance, name, nextIndex, transaction
302
);
303
}
304
nextIndex++;
305
}
306
// Remove children that are no longer present.
307
for (name in prevChildren) {
308
if (prevChildren.hasOwnProperty(name) &&
309
!(nextChildren && nextChildren[name])) {
310
this._unmountChildByName(prevChildren[name], name);
311
}
312
}
313
},
314
315
/**
316
* Unmounts all rendered children. This should be used to clean up children
317
* when this component is unmounted.
318
*
319
* @internal
320
*/
321
unmountChildren: function() {
322
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
}
330
this._renderedChildren = null;
331
},
332
333
/**
334
* Moves a child component to the supplied index.
335
*
336
* @param {ReactComponent} child Component to move.
337
* @param {number} toIndex Destination index of the element.
338
* @param {number} lastIndex Last index visited of the siblings of `child`.
339
* @protected
340
*/
341
moveChild: function(child, toIndex, lastIndex) {
342
// If the index of `child` is less than `lastIndex`, then it needs to
343
// be moved. Otherwise, we do not need to move it because a child will be
344
// inserted or moved before `child`.
345
if (child._mountIndex < lastIndex) {
346
enqueueMove(this._rootNodeID, child._mountIndex, toIndex);
347
}
348
},
349
350
/**
351
* Creates a child component.
352
*
353
* @param {ReactComponent} child Component to create.
354
* @param {string} mountImage Markup to insert.
355
* @protected
356
*/
357
createChild: function(child, mountImage) {
358
enqueueMarkup(this._rootNodeID, mountImage, child._mountIndex);
359
},
360
361
/**
362
* Removes a child component.
363
*
364
* @param {ReactComponent} child Child to remove.
365
* @protected
366
*/
367
removeChild: function(child) {
368
enqueueRemove(this._rootNodeID, child._mountIndex);
369
},
370
371
/**
372
* Sets this text content string.
373
*
374
* @param {string} textContent Text content to set.
375
* @protected
376
*/
377
setTextContent: function(textContent) {
378
enqueueTextContent(this._rootNodeID, textContent);
379
},
380
381
/**
382
* Mounts a child with the supplied name.
383
*
384
* NOTE: This is part of `updateChildren` and is here for readability.
385
*
386
* @param {ReactComponent} child Component to mount.
387
* @param {string} name Name of the child.
388
* @param {number} index Index at which to insert the child.
389
* @param {ReactReconcileTransaction} transaction
390
* @private
391
*/
392
_mountChildByNameAtIndex: function(child, name, index, transaction) {
393
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
394
var rootID = this._rootNodeID + name;
395
var mountImage = child.mountComponent(
396
rootID,
397
transaction,
398
this._mountDepth + 1
399
);
400
child._mountIndex = index;
401
this.createChild(child, mountImage);
402
this._renderedChildren = this._renderedChildren || {};
403
this._renderedChildren[name] = child;
404
},
405
406
/**
407
* Unmounts a rendered child by name.
408
*
409
* NOTE: This is part of `updateChildren` and is here for readability.
410
*
411
* @param {ReactComponent} child Component to unmount.
412
* @param {string} name Name of the child in `this._renderedChildren`.
413
* @private
414
*/
415
_unmountChildByName: function(child, name) {
416
this.removeChild(child);
417
child._mountIndex = null;
418
child.unmountComponent();
419
delete this._renderedChildren[name];
420
}
421
422
}
423
424
};
425
426
module.exports = ReactMultiChild;
427
428