Don't reconcile children unneccessarily
We've talked about this perf optimization for a while now. This prevents us from re-reconciling components that are above the component being reconciled in the owner hierarchy.
Pete Hunt committed
Jan 23, 2014 at 13:17 UTC
62b52e008e139a75267a40c626d76683bca7f3ab
2 files changed
+54
-10
src/core/ReactCompositeComponent.js
+7
@@ -1060,6 +1060,13 @@ var ReactCompositeComponentMixin = {
1060
},
1061
1062
receiveComponent: function(nextComponent, transaction) {
1063
+ if (nextComponent === this) {
1064
+ // Since props and context are immutable after the component is
1065
+ // mounted, we can do a cheap identity compare here to determine
1066
+ // if this is a superfluous reconcile.
1067
+ return;
1068
+ }
1069
+
1070
this._pendingContext = nextComponent._currentContext;
1071
ReactComponent.Mixin.receiveComponent.call(
1072
this,
src/core/__tests__/ReactUpdates-test.js
+47
-10
@@ -338,6 +338,39 @@ describe('ReactUpdates', function() {
338
expect(childRenderCount).toBe(2);
339
});
340
341
+ it('should not reconcile children passed via props', function() {
342
+ var numMiddleRenders = 0;
343
+ var numBottomRenders = 0;
344
+
345
+ var Top = React.createClass({
346
+ render: function() {
347
+ return <Middle><Bottom /></Middle>;
348
+ }
349
+ });
350
+
351
+ var Middle = React.createClass({
352
+ componentDidMount: function() {
353
+ this.forceUpdate();
354
+ },
355
+
356
+ render: function() {
357
+ numMiddleRenders++;
358
+ return <div>{this.props.children}</div>;
359
+ }
360
+ });
361
+
362
+ var Bottom = React.createClass({
363
+ render: function() {
364
+ numBottomRenders++;
365
+ return <span />;
366
+ }
367
+ });
368
+
369
+ ReactTestUtils.renderIntoDocument(<Top />);
370
+ expect(numMiddleRenders).toBe(2);
371
+ expect(numBottomRenders).toBe(1);
372
+ });
373
+
374
it('should flow updates correctly', function() {
375
var willUpdates = [];
376
var didUpdates = [];
@@ -406,11 +439,9 @@ describe('ReactUpdates', function() {
439
var root = <App />;
440
ReactTestUtils.renderIntoDocument(root);
441
409
- function expectUpdates(sequence) {
410
- // didUpdate() occurs in reverse order
411
- didUpdates.reverse();
412
- expect(willUpdates).toEqual(didUpdates);
413
- expect(willUpdates).toEqual(sequence);
442
+ function expectUpdates(desiredWillUpdates, desiredDidUpdates) {
443
+ expect(willUpdates).toEqual(desiredWillUpdates);
444
+ expect(didUpdates).toEqual(desiredDidUpdates);
445
willUpdates.length = 0;
446
didUpdates.length = 0;
447
}
@@ -419,7 +450,7 @@ describe('ReactUpdates', function() {
450
c.setState({x: 1});
451
}
452
422
- function testUpdates(components, expectation) {
453
+ function testUpdates(components, desiredWillUpdates, desiredDidUpdates) {
454
var i;
455
456
ReactUpdates.batchedUpdates(function() {
@@ -428,7 +459,7 @@ describe('ReactUpdates', function() {
459
}
460
});
461
431
- expectUpdates(expectation);
462
+ expectUpdates(desiredWillUpdates, desiredDidUpdates);
463
464
// Try them in reverse order
465
@@ -438,22 +469,28 @@ describe('ReactUpdates', function() {
469
}
470
});
471
441
- expectUpdates(expectation);
472
+ expectUpdates(desiredWillUpdates, desiredDidUpdates);
473
}
474
475
testUpdates(
476
[root.refs.switcher.refs.box, root.refs.switcher],
446
- ['Switcher', 'Box', 'Child']
477
+ // Owner-child relationships have inverse will and did
478
+ ['Switcher', 'Box'],
479
+ ['Box', 'Switcher']
480
);
481
482
testUpdates(
483
[root.refs.child, root.refs.switcher.refs.box],
484
+ // Not owner-child so reconcile independently
485
+ ['Box', 'Child'],
486
['Box', 'Child']
487
);
488
489
testUpdates(
490
[root.refs.child, root.refs.switcher],
456
- ['Switcher', 'Box', 'Child']
491
+ // Switcher owns Box and Child, Box does not own Child
492
+ ['Switcher', 'Box', 'Child'],
493
+ ['Box', 'Switcher', 'Child']
494
);
495
});
496
});