Kill global queue in ReactMultiChild
As we move away from using HTML for updates, this becomes less useful.
Ben Alpert committed
Nov 25, 2015 at 12:56 UTC
418ba27485ef55dfbee06a3f9e56f09380e09a30
5 files changed
+112
-220
src/renderers/dom/client/ReactDOMIDOperations.js
+3
-8
@@ -25,16 +25,11 @@ var ReactDOMIDOperations = {
25
* Updates a component's children by processing a series of updates.
26
*
27
* @param {array<object>} updates List of update configurations.
28
- * @param {array<string>} markup List of markup strings.
28
* @internal
29
*/
31
- dangerouslyProcessChildrenUpdates: function(updates, markup) {
32
- for (var i = 0; i < updates.length; i++) {
33
- var update = updates[i];
34
- var node = ReactDOMComponentTree.getNodeFromInstance(update.parentInst);
35
- update.parentNode = node;
36
- }
37
- DOMChildrenOperations.processUpdates(updates, markup);
30
+ dangerouslyProcessChildrenUpdates: function(parentInst, updates) {
31
+ var node = ReactDOMComponentTree.getNodeFromInstance(parentInst);
32
+ DOMChildrenOperations.processUpdates(node, updates);
33
},
34
};
35
src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js
+1
-3
@@ -20,11 +20,9 @@ describe('ReactDOMIDOperations', function() {
20
var html = '\n \t <span> \n testContent \t </span> \n \t';
21
22
ReactDOMIDOperations.dangerouslyProcessChildrenUpdates(
23
+ {_nativeNode: stubNode},
24
[{
24
- parentInst: {_nativeNode: stubNode},
25
- parentNode: null,
25
type: ReactMultiChildUpdateTypes.SET_MARKUP,
27
- markupIndex: null,
26
content: html,
27
fromIndex: null,
28
toIndex: null,
src/renderers/dom/client/utils/DOMChildrenOperations.js
+25
-25
@@ -67,58 +67,58 @@ var DOMChildrenOperations = {
67
* update configurations are each expected to have a `parentNode` property.
68
*
69
* @param {array<object>} updates List of update configurations.
70
- * @param {array<string>} markupList List of markup strings.
70
* @internal
71
*/
73
- processUpdates: function(updates, markupList) {
72
+ processUpdates: function(parentNode, updates) {
73
var update;
74
// Mapping from parent IDs to initial child orderings.
75
var initialChildren = null;
76
// List of children that will be moved or removed.
77
var updatedChildren = null;
78
79
+ var markupList = null;
80
+
81
for (var i = 0; i < updates.length; i++) {
82
update = updates[i];
83
if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING ||
84
update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) {
85
var updatedIndex = update.fromIndex;
85
- var updatedChild = update.parentNode.childNodes[updatedIndex];
86
- var parentID = update.parentInst._rootNodeID;
86
+ var updatedChild = parentNode.childNodes[updatedIndex];
87
88
invariant(
89
updatedChild,
90
- 'processUpdates(): Unable to find child %s of element. This ' +
90
+ 'processUpdates(): Unable to find child %s of element %s. This ' +
91
'probably means the DOM was unexpectedly mutated (e.g., by the ' +
92
'browser), usually due to forgetting a <tbody> when using tables, ' +
93
'nesting tags like <form>, <p>, or <a>, or using non-SVG elements ' +
94
- 'in an <svg> parent. Try inspecting the child nodes of the element ' +
95
- 'with React ID `%s`.',
94
+ 'in an <svg> parent.',
95
updatedIndex,
97
- parentID
96
+ parentNode,
97
);
98
99
initialChildren = initialChildren || {};
101
- initialChildren[parentID] = initialChildren[parentID] || [];
102
- initialChildren[parentID][updatedIndex] = updatedChild;
100
+ initialChildren[updatedIndex] = updatedChild;
101
102
updatedChildren = updatedChildren || [];
103
updatedChildren.push(updatedChild);
104
+ } else if (update.type === ReactMultiChildUpdateTypes.INSERT_MARKUP) {
105
+ // Replace each HTML string with an index into the markup list
106
+ if (typeof update.content === 'string') {
107
+ markupList = markupList || [];
108
+ update.content = markupList.push(update.markup);
109
+ }
110
}
111
}
112
109
- // markupList is either a list of markup or just a list of elements
110
- var isHTML = markupList.length && typeof markupList[0] === 'string';
113
var renderedMarkup;
112
- if (isHTML) {
114
+ if (markupList) {
115
renderedMarkup = Danger.dangerouslyRenderMarkup(markupList);
114
- } else {
115
- renderedMarkup = markupList;
116
}
117
118
// Remove updated children first so that `toIndex` is consistent.
119
if (updatedChildren) {
120
for (var j = 0; j < updatedChildren.length; j++) {
121
- updatedChildren[j].parentNode.removeChild(updatedChildren[j]);
121
+ parentNode.removeChild(updatedChildren[j]);
122
}
123
}
124
@@ -126,36 +126,36 @@ var DOMChildrenOperations = {
126
update = updates[k];
127
switch (update.type) {
128
case ReactMultiChildUpdateTypes.INSERT_MARKUP:
129
- if (isHTML) {
129
+ if (renderedMarkup) {
130
insertChildAt(
131
- update.parentNode,
132
- renderedMarkup[update.markupIndex],
131
+ parentNode,
132
+ renderedMarkup[update.content],
133
update.toIndex
134
);
135
} else {
136
insertLazyTreeChildAt(
137
- update.parentNode,
138
- renderedMarkup[update.markupIndex],
137
+ parentNode,
138
+ update.content,
139
update.toIndex
140
);
141
}
142
break;
143
case ReactMultiChildUpdateTypes.MOVE_EXISTING:
144
insertChildAt(
145
- update.parentNode,
146
- initialChildren[update.parentInst._rootNodeID][update.fromIndex],
145
+ parentNode,
146
+ initialChildren[update.fromIndex],
147
update.toIndex
148
);
149
break;
150
case ReactMultiChildUpdateTypes.SET_MARKUP:
151
setInnerHTML(
152
- update.parentNode,
152
+ parentNode,
153
update.content
154
);
155
break;
156
case ReactMultiChildUpdateTypes.TEXT_CONTENT:
157
setTextContent(
158
- update.parentNode,
158
+ parentNode,
159
update.content
160
);
161
break;
src/renderers/shared/reconciler/ReactMultiChild.js
+79
-177
@@ -22,157 +22,109 @@ var ReactChildReconciler = require('ReactChildReconciler');
22
var flattenChildren = require('flattenChildren');
23
24
/**
25
- * Updating children of a component may trigger recursive updates. The depth is
26
- * used to batch recursive updates to render markup more efficiently.
25
+ * Make an update for markup to be rendered and inserted at a supplied index.
26
*
28
- * @type {number}
29
- * @private
30
- */
31
-var updateDepth = 0;
32
-
33
-/**
34
- * Queue of update configuration objects.
35
- *
36
- * Each object has a `type` property that is in `ReactMultiChildUpdateTypes`.
37
- *
38
- * @type {array<object>}
39
- * @private
40
- */
41
-var updateQueue = [];
42
-
43
-/**
44
- * Queue of markup to be rendered.
45
- *
46
- * @type {array<string>}
47
- * @private
48
- */
49
-var markupQueue = [];
50
-
51
-/**
52
- * Enqueues markup to be rendered and inserted at a supplied index.
53
- *
54
- * @param {object} parentInst parent component.
27
* @param {string} markup Markup that renders into an element.
28
* @param {number} toIndex Destination index.
29
* @private
30
*/
59
-function enqueueInsertMarkup(parentInst, markup, toIndex) {
31
+function makeInsertMarkup(markup, toIndex) {
32
// NOTE: Null values reduce hidden classes.
61
- updateQueue.push({
62
- parentInst: parentInst,
63
- parentNode: null,
33
+ return {
34
type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
65
- markupIndex: markupQueue.push(markup) - 1,
66
- content: null,
35
+ content: markup,
36
fromIndex: null,
37
toIndex: toIndex,
69
- });
38
+ };
39
}
40
41
/**
73
- * Enqueues moving an existing element to another index.
42
+ * Make an update for moving an existing element to another index.
43
*
75
- * @param {object} parentInst parent component.
44
* @param {number} fromIndex Source index of the existing element.
45
* @param {number} toIndex Destination index of the element.
46
* @private
47
*/
80
-function enqueueMove(parentInst, fromIndex, toIndex) {
48
+function makeMove(fromIndex, toIndex) {
49
// NOTE: Null values reduce hidden classes.
82
- updateQueue.push({
83
- parentInst: parentInst,
84
- parentNode: null,
50
+ return {
51
type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
86
- markupIndex: null,
52
content: null,
53
fromIndex: fromIndex,
54
toIndex: toIndex,
90
- });
55
+ };
56
}
57
58
/**
94
- * Enqueues removing an element at an index.
59
+ * Make an update for removing an element at an index.
60
*
96
- * @param {object} parentInst parent component.
61
* @param {number} fromIndex Index of the element to remove.
62
* @private
63
*/
100
-function enqueueRemove(parentInst, fromIndex) {
64
+function makeRemove(fromIndex) {
65
// NOTE: Null values reduce hidden classes.
102
- updateQueue.push({
103
- parentInst: parentInst,
104
- parentNode: null,
66
+ return {
67
type: ReactMultiChildUpdateTypes.REMOVE_NODE,
106
- markupIndex: null,
68
content: null,
69
fromIndex: fromIndex,
70
toIndex: null,
110
- });
71
+ };
72
}
73
74
/**
114
- * Enqueues setting the markup of a node.
75
+ * Make an update for setting the markup of a node.
76
*
116
- * @param {object} parentInst parent component.
77
* @param {string} markup Markup that renders into an element.
78
* @private
79
*/
120
-function enqueueSetMarkup(parentInst, markup) {
80
+function makeSetMarkup(markup) {
81
// NOTE: Null values reduce hidden classes.
122
- updateQueue.push({
123
- parentInst: parentInst,
124
- parentNode: null,
82
+ return {
83
type: ReactMultiChildUpdateTypes.SET_MARKUP,
126
- markupIndex: null,
84
content: markup,
85
fromIndex: null,
86
toIndex: null,
130
- });
87
+ };
88
}
89
90
/**
134
- * Enqueues setting the text content.
91
+ * Make an update for setting the text content.
92
*
136
- * @param {object} parentInst parent component.
93
* @param {string} textContent Text content to set.
94
* @private
95
*/
140
-function enqueueTextContent(parentInst, textContent) {
96
+function makeTextContent(textContent) {
97
// NOTE: Null values reduce hidden classes.
142
- updateQueue.push({
143
- parentInst: parentInst,
144
- parentNode: null,
98
+ return {
99
type: ReactMultiChildUpdateTypes.TEXT_CONTENT,
146
- markupIndex: null,
100
content: textContent,
101
fromIndex: null,
102
toIndex: null,
150
- });
103
+ };
104
}
105
106
/**
154
- * Processes any enqueued updates.
155
- *
156
- * @private
107
+ * Push an update, if any, onto the queue. Creates a new queue if none is
108
+ * passed and always returns the queue. Mutative.
109
*/
158
-function processQueue() {
159
- if (updateQueue.length) {
160
- ReactComponentEnvironment.processChildrenUpdates(
161
- updateQueue,
162
- markupQueue
163
- );
164
- clearQueue();
110
+function enqueue(queue, update) {
111
+ if (update) {
112
+ queue = queue || [];
113
+ queue.push(update);
114
}
115
+ return queue;
116
}
117
118
/**
169
- * Clears any enqueued updates.
119
+ * Processes any enqueued updates.
120
*
121
* @private
122
*/
173
-function clearQueue() {
174
- updateQueue.length = 0;
175
- markupQueue.length = 0;
123
+function processQueue(inst, updateQueue) {
124
+ ReactComponentEnvironment.processChildrenUpdates(
125
+ inst,
126
+ updateQueue,
127
+ );
128
}
129
130
/**
@@ -270,31 +222,19 @@ var ReactMultiChild = {
222
* @internal
223
*/
224
updateTextContent: function(nextContent) {
273
- updateDepth++;
274
- var errorThrown = true;
275
- try {
276
- var prevChildren = this._renderedChildren;
277
- // Remove any rendered children.
278
- ReactChildReconciler.unmountChildren(prevChildren);
279
- // TODO: The setTextContent operation should be enough
280
- for (var name in prevChildren) {
281
- if (prevChildren.hasOwnProperty(name)) {
282
- this._unmountChild(prevChildren[name]);
283
- }
284
- }
285
- // Set new text content.
286
- this.setTextContent(nextContent);
287
- errorThrown = false;
288
- } finally {
289
- updateDepth--;
290
- if (!updateDepth) {
291
- if (errorThrown) {
292
- clearQueue();
293
- } else {
294
- processQueue();
295
- }
225
+ var prevChildren = this._renderedChildren;
226
+ // Remove any rendered children.
227
+ ReactChildReconciler.unmountChildren(prevChildren);
228
+ // TODO: The setTextContent operation should be enough
229
+ var updates = [];
230
+ for (var name in prevChildren) {
231
+ if (prevChildren.hasOwnProperty(name)) {
232
+ updates.push(this._unmountChild(prevChildren[name]));
233
}
234
}
235
+ // Set new text content.
236
+ updates.push(makeTextContent(nextContent));
237
+ processQueue(this, updates);
238
},
239
240
/**
@@ -304,29 +244,17 @@ var ReactMultiChild = {
244
* @internal
245
*/
246
updateMarkup: function(nextMarkup) {
307
- updateDepth++;
308
- var errorThrown = true;
309
- try {
310
- var prevChildren = this._renderedChildren;
311
- // Remove any rendered children.
312
- ReactChildReconciler.unmountChildren(prevChildren);
313
- for (var name in prevChildren) {
314
- if (prevChildren.hasOwnProperty(name)) {
315
- this._unmountChild(prevChildren[name]);
316
- }
317
- }
318
- this.setMarkup(nextMarkup);
319
- errorThrown = false;
320
- } finally {
321
- updateDepth--;
322
- if (!updateDepth) {
323
- if (errorThrown) {
324
- clearQueue();
325
- } else {
326
- processQueue();
327
- }
247
+ var prevChildren = this._renderedChildren;
248
+ // Remove any rendered children.
249
+ ReactChildReconciler.unmountChildren(prevChildren);
250
+ var updates = [];
251
+ for (var name in prevChildren) {
252
+ if (prevChildren.hasOwnProperty(name)) {
253
+ updates.push(this._unmountChild(prevChildren[name]));
254
}
255
}
256
+ updates.push(makeSetMarkup(nextMarkup));
257
+ processQueue(this, updates);
258
},
259
260
/**
@@ -337,27 +265,11 @@ var ReactMultiChild = {
265
* @internal
266
*/
267
updateChildren: function(nextNestedChildrenElements, transaction, context) {
340
- updateDepth++;
341
- var errorThrown = true;
342
- try {
343
- this._updateChildren(nextNestedChildrenElements, transaction, context);
344
- errorThrown = false;
345
- } finally {
346
- updateDepth--;
347
- if (!updateDepth) {
348
- if (errorThrown) {
349
- clearQueue();
350
- } else {
351
- processQueue();
352
- }
353
- }
354
- }
268
+ // Hook used by React ART
269
+ this._updateChildren(nextNestedChildrenElements, transaction, context);
270
},
271
272
/**
358
- * Improve performance by isolating this hot code path from the try/catch
359
- * block in `updateChildren`.
360
- *
273
* @param {?object} nextNestedChildrenElements Nested child element maps.
274
* @param {ReactReconcileTransaction} transaction
275
* @final
@@ -371,6 +283,7 @@ var ReactMultiChild = {
283
if (!nextChildren && !prevChildren) {
284
return;
285
}
286
+ var updates = null;
287
var name;
288
// `nextIndex` will increment for each child in `nextChildren`, but
289
// `lastIndex` will be the last index visited in `prevChildren`.
@@ -383,18 +296,22 @@ var ReactMultiChild = {
296
var prevChild = prevChildren && prevChildren[name];
297
var nextChild = nextChildren[name];
298
if (prevChild === nextChild) {
386
- this.moveChild(prevChild, nextIndex, lastIndex);
299
+ updates = enqueue(
300
+ updates,
301
+ this.moveChild(prevChild, nextIndex, lastIndex)
302
+ );
303
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
304
prevChild._mountIndex = nextIndex;
305
} else {
306
if (prevChild) {
307
// Update `lastIndex` before `_mountIndex` gets unset by unmounting.
308
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
393
- this._unmountChild(prevChild);
309
+ updates = enqueue(updates, this._unmountChild(prevChild));
310
}
311
// The child must be instantiated before it's mounted.
396
- this._mountChildAtIndex(
397
- nextChild, nextIndex, transaction, context
312
+ updates = enqueue(
313
+ updates,
314
+ this._mountChildAtIndex(nextChild, nextIndex, transaction, context)
315
);
316
}
317
nextIndex++;
@@ -403,9 +320,16 @@ var ReactMultiChild = {
320
for (name in prevChildren) {
321
if (prevChildren.hasOwnProperty(name) &&
322
!(nextChildren && nextChildren.hasOwnProperty(name))) {
406
- this._unmountChild(prevChildren[name]);
323
+ updates = enqueue(
324
+ updates,
325
+ this._unmountChild(prevChildren[name])
326
+ );
327
}
328
}
329
+ if (updates) {
330
+ this.prepareToManageChildren();
331
+ processQueue(this, updates);
332
+ }
333
this._renderedChildren = nextChildren;
334
},
335
@@ -442,8 +366,7 @@ var ReactMultiChild = {
366
// be moved. Otherwise, we do not need to move it because a child will be
367
// inserted or moved before `child`.
368
if (child._mountIndex < lastIndex) {
445
- this.prepareToManageChildren();
446
- enqueueMove(this, child._mountIndex, toIndex);
369
+ return makeMove(child._mountIndex, toIndex);
370
}
371
},
372
@@ -455,8 +378,7 @@ var ReactMultiChild = {
378
* @protected
379
*/
380
createChild: function(child, mountImage) {
458
- this.prepareToManageChildren();
459
- enqueueInsertMarkup(this, mountImage, child._mountIndex);
381
+ return makeInsertMarkup(mountImage, child._mountIndex);
382
},
383
384
/**
@@ -466,28 +388,7 @@ var ReactMultiChild = {
388
* @protected
389
*/
390
removeChild: function(child) {
469
- this.prepareToManageChildren();
470
- enqueueRemove(this, child._mountIndex);
471
- },
472
-
473
- /**
474
- * Sets this text content string.
475
- *
476
- * @param {string} textContent Text content to set.
477
- * @protected
478
- */
479
- setTextContent: function(textContent) {
480
- enqueueTextContent(this, textContent);
481
- },
482
-
483
- /**
484
- * Sets this markup string.
485
- *
486
- * @param {string} markup Markup to set.
487
- * @protected
488
- */
489
- setMarkup: function(markup) {
490
- enqueueSetMarkup(this, markup);
391
+ return makeRemove(child._mountIndex);
392
},
393
394
/**
@@ -514,7 +415,7 @@ var ReactMultiChild = {
415
context
416
);
417
child._mountIndex = index;
517
- this.createChild(child, mountImage);
418
+ return this.createChild(child, mountImage);
419
},
420
421
/**
@@ -526,8 +427,9 @@ var ReactMultiChild = {
427
* @private
428
*/
429
_unmountChild: function(child) {
529
- this.removeChild(child);
430
+ var update = this.removeChild(child);
431
child._mountIndex = null;
432
+ return update;
433
},
434
435
},
src/test/ReactDefaultPerf.js
+4
-7
@@ -199,7 +199,7 @@ var ReactDefaultPerf = {
199
ReactDefaultPerf._recordWrite('', fnName, totalTime, args[0]);
200
} else if (fnName === 'dangerouslyProcessChildrenUpdates') {
201
// special format
202
- args[0].forEach(function(update) {
202
+ args[1].forEach(function(update) {
203
var writeArgs = {};
204
if (update.fromIndex !== null) {
205
writeArgs.fromIndex = update.fromIndex;
@@ -207,14 +207,11 @@ var ReactDefaultPerf = {
207
if (update.toIndex !== null) {
208
writeArgs.toIndex = update.toIndex;
209
}
210
- if (update.textContent !== null) {
211
- writeArgs.textContent = update.textContent;
212
- }
213
- if (update.markupIndex !== null) {
214
- writeArgs.markup = args[1][update.markupIndex];
210
+ if (update.content !== null) {
211
+ writeArgs.content = update.content;
212
}
213
ReactDefaultPerf._recordWrite(
217
- update.parentInst._rootNodeID,
214
+ args[0]._rootNodeID,
215
update.type,
216
totalTime,
217
writeArgs