@samitouri / QOS-React / commits / 418ba27485

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