@samitouri / QOS-React-2 / commits / 289962f6f8

Don't bail out of reconsiliation if context changes.

Jim committed Jun 24, 2015 at 21:14 UTC 289962f6f868df1ccb4620912c7fd806f98a7e47
5 files changed +119 -24
examples/basic-jsx/index.html
+46 -15
@@ -29,23 +29,54 @@
29 <script src="../../build/react.js"></script>
30 <script src="../../build/JSXTransformer.js"></script>
31 <script type="text/jsx">
32 - var ExampleApplication = React.createClass({
33 - render: function() {
34 - var elapsed = Math.round(this.props.elapsed / 100);
35 - var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
36 - var message =
37 - 'React has been successfully running for ' + seconds + ' seconds.';
32
39 - return <p>{message}</p>;
40 - }
33 +var Top = React.createClass({
34 + childContextTypes: {
35 + now: React.PropTypes.any
36 + },
37 +
38 + getChildContext: function() {
39 + return {
40 + now: this.state.now
41 + };
42 + },
43 +
44 + getInitialState: function() {
45 + return {
46 + now: Date.now()
47 + };
48 + },
49 +
50 + componentDidMount: function() {
51 + setInterval(function() {
52 + this.setState({
53 + now: Date.now()
54 });
42 - var start = new Date().getTime();
43 - setInterval(function() {
44 - React.render(
45 - <ExampleApplication elapsed={new Date().getTime() - start} />,
46 - document.getElementById('container')
47 - );
48 - }, 50);
55 + }.bind(this), 500);
56 + },
57 +
58 + render: function() {
59 + return (
60 + <div>
61 + <h1>{this.state.now}</h1>
62 + <Child />{this.props.children}
63 + </div>
64 + );
65 + }
66 +});
67 +
68 +var Child = React.createClass({
69 + contextTypes: {
70 + now: React.PropTypes.any
71 + },
72 +
73 + render: function() {
74 + // I never update
75 + return <h2>{this.context.now}</h2>;
76 + }
77 +});
78 +
79 +React.render(<Top><Child/></Top>, document.getElementById('container'));
80 </script>
81 </body>
82 </html>
src/renderers/shared/reconciler/ReactCompositeComponent.js
+3 -4
@@ -519,19 +519,18 @@ var ReactCompositeComponentMixin = {
519 ) {
520 var inst = this._instance;
521
522 - var nextContext;
522 + var nextContext = this._context === nextUnmaskedContext ?
523 + inst.context :
524 + this._processContext(nextUnmaskedContext);
525 var nextProps;
526
527 // Distinguish between a props update versus a simple state update
528 if (prevParentElement === nextParentElement) {
527 - nextContext = inst.context;
529 // Skip checking prop types again -- we don't read inst.props to avoid
530 // warning for DOM component props in this upgrade
531 nextProps = nextParentElement.props;
532 } else {
532 - nextContext = this._processContext(nextUnmaskedContext);
533 nextProps = this._processProps(nextParentElement.props);
534 -
534 // An update here will schedule an update but immediately set
535 // _pendingStateQueue which will ensure that any state updates gets
536 // immediately reconciled instead of waiting for the next batch.
src/renderers/shared/reconciler/ReactReconciler.js
+7 -2
@@ -65,8 +65,10 @@ var ReactReconciler = {
65 internalInstance, nextElement, transaction, context
66 ) {
67 var prevElement = internalInstance._currentElement;
68 -
69 - if (nextElement === prevElement && nextElement._owner != null) {
68 + if (nextElement === prevElement &&
69 + nextElement._owner != null
70 + // TODO: Shouldn't we need to do this: `&& context === internalInstance._context`
71 + ) {
72 // Since elements are immutable after the owner is rendered,
73 // we can do a cheap identity compare here to determine if this is a
74 // superfluous reconcile. It's possible for state to be mutable but such
@@ -74,6 +76,9 @@ var ReactReconciler = {
76 // the element. We explicitly check for the existence of an owner since
77 // it's possible for an element created outside a composite to be
78 // deeply mutated and reused.
79 +
80 + // TODO: Bailing out early is just a perf optimization right?
81 + // TODO: Removing the return statement should affect correctness?
82 return;
83 }
84
src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js
+61
@@ -609,6 +609,67 @@ describe('ReactCompositeComponent', function() {
609 expect(React.findDOMNode(component).innerHTML).toBe('bar');
610 });
611
612 + it('should pass context when re-rendered for static child', function() {
613 + var parentInstance = null;
614 + var childInstance = null;
615 +
616 + var Parent = React.createClass({
617 + childContextTypes: {
618 + foo: ReactPropTypes.string,
619 + flag: ReactPropTypes.bool,
620 + },
621 +
622 + getChildContext: function() {
623 + return {
624 + foo: 'bar',
625 + flag: this.state.flag,
626 + };
627 + },
628 +
629 + getInitialState: function() {
630 + return {
631 + flag: false,
632 + };
633 + },
634 +
635 + render: function() {
636 + return React.Children.only(this.props.children);
637 + },
638 + });
639 +
640 + var Middle = React.createClass({
641 + render: function() {
642 + return this.props.children;
643 + },
644 + });
645 +
646 + var Child = React.createClass({
647 + contextTypes: {
648 + foo: ReactPropTypes.string,
649 + flag: ReactPropTypes.bool,
650 + },
651 +
652 + render: function() {
653 + childInstance = this;
654 + return <span>Child</span>;
655 + },
656 + });
657 +
658 + parentInstance = ReactTestUtils.renderIntoDocument(
659 + <Parent><Middle><Child /></Middle></Parent>
660 + );
661 +
662 + expect(parentInstance.state.flag).toBe(false);
663 + reactComponentExpect(childInstance).scalarContextEqual({foo: 'bar', flag: false});
664 +
665 + parentInstance.setState({flag: true});
666 + expect(parentInstance.state.flag).toBe(true);
667 +
668 + expect(console.error.argsForCall.length).toBe(0);
669 +
670 + reactComponentExpect(childInstance).scalarContextEqual({foo: 'bar', flag: true});
671 + });
672 +
673 it('should pass context transitively', function() {
674 var childInstance = null;
675 var grandchildInstance = null;
src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js
+2 -3
@@ -347,14 +347,14 @@ describe('ReactUpdates', function() {
347
348 render: function() {
349 numMiddleRenders++;
350 - return <div>{this.props.children}</div>;
350 + return React.Children.only(this.props.children);
351 },
352 });
353
354 var Bottom = React.createClass({
355 render: function() {
356 numBottomRenders++;
357 - return <span />;
357 + return null;
358 },
359 });
360
@@ -463,7 +463,6 @@ describe('ReactUpdates', function() {
463
464 expectUpdates(desiredWillUpdates, desiredDidUpdates);
465 }
466 -
466 testUpdates(
467 [root.refs.switcher.refs.box, root.refs.switcher],
468 // Owner-child relationships have inverse will and did