@samitouri / QOS-React / commits / 5e3706cca0

Don't render bitmask-bailing consumers even if there's a deeper matching child (#12543)

* Don't render consumers that bailed out with bitmask even if there's a deeper matching child * Use a render prop in the test Without it, <Indirection> doesn't do anything because we bail out on constant element anyway. That's not what we're testing, and could be confusing.

Dan Abramov committed Apr 4, 2018 at 19:44 UTC 5e3706cca0fe0da462c771d14a271cd2961e5718
2 files changed +124
packages/react-reconciler/src/ReactFiberBeginWork.js
+4
@@ -994,6 +994,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
994 changedBits,
995 renderExpirationTime,
996 );
997 + } else if (oldProps === newProps) {
998 + // Skip over a memoized parent with a bitmask bailout even
999 + // if we began working on it because of a deeper matching child.
1000 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
1001 }
1002 // There is no bailout on `children` equality because we expect people
1003 // to often pass a bound method as a child, but it may reference
packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js
+120
@@ -580,6 +580,126 @@ describe('ReactNewContext', () => {
580 expect(ReactNoop.getChildren()).toEqual([span('Foo: 3'), span('Bar: 3')]);
581 });
582
583 + it('can skip parents with bitmask bailout while updating their children', () => {
584 + const Context = React.createContext({foo: 0, bar: 0}, (a, b) => {
585 + let result = 0;
586 + if (a.foo !== b.foo) {
587 + result |= 0b01;
588 + }
589 + if (a.bar !== b.bar) {
590 + result |= 0b10;
591 + }
592 + return result;
593 + });
594 +
595 + function Provider(props) {
596 + return (
597 + <Context.Provider value={{foo: props.foo, bar: props.bar}}>
598 + {props.children}
599 + </Context.Provider>
600 + );
601 + }
602 +
603 + function Foo(props) {
604 + return (
605 + <Context.Consumer unstable_observedBits={0b01}>
606 + {value => {
607 + ReactNoop.yield('Foo');
608 + return (
609 + <React.Fragment>
610 + <span prop={'Foo: ' + value.foo} />
611 + {props.children && props.children()}
612 + </React.Fragment>
613 + );
614 + }}
615 + </Context.Consumer>
616 + );
617 + }
618 +
619 + function Bar(props) {
620 + return (
621 + <Context.Consumer unstable_observedBits={0b10}>
622 + {value => {
623 + ReactNoop.yield('Bar');
624 + return (
625 + <React.Fragment>
626 + <span prop={'Bar: ' + value.bar} />
627 + {props.children && props.children()}
628 + </React.Fragment>
629 + );
630 + }}
631 + </Context.Consumer>
632 + );
633 + }
634 +
635 + class Indirection extends React.Component {
636 + shouldComponentUpdate() {
637 + return false;
638 + }
639 + render() {
640 + return this.props.children;
641 + }
642 + }
643 +
644 + function App(props) {
645 + return (
646 + <Provider foo={props.foo} bar={props.bar}>
647 + <Indirection>
648 + <Foo>
649 + {/* Use a render prop so we don't test constant elements. */}
650 + {() => (
651 + <Indirection>
652 + <Bar>
653 + {() => (
654 + <Indirection>
655 + <Foo />
656 + </Indirection>
657 + )}
658 + </Bar>
659 + </Indirection>
660 + )}
661 + </Foo>
662 + </Indirection>
663 + </Provider>
664 + );
665 + }
666 +
667 + ReactNoop.render(<App foo={1} bar={1} />);
668 + expect(ReactNoop.flush()).toEqual(['Foo', 'Bar', 'Foo']);
669 + expect(ReactNoop.getChildren()).toEqual([
670 + span('Foo: 1'),
671 + span('Bar: 1'),
672 + span('Foo: 1'),
673 + ]);
674 +
675 + // Update only foo
676 + ReactNoop.render(<App foo={2} bar={1} />);
677 + expect(ReactNoop.flush()).toEqual(['Foo', 'Foo']);
678 + expect(ReactNoop.getChildren()).toEqual([
679 + span('Foo: 2'),
680 + span('Bar: 1'),
681 + span('Foo: 2'),
682 + ]);
683 +
684 + // Update only bar
685 + ReactNoop.render(<App foo={2} bar={2} />);
686 + expect(ReactNoop.flush()).toEqual(['Bar']);
687 + expect(ReactNoop.getChildren()).toEqual([
688 + span('Foo: 2'),
689 + span('Bar: 2'),
690 + span('Foo: 2'),
691 + ]);
692 +
693 + // Update both
694 + ReactNoop.render(<App foo={3} bar={3} />);
695 + expect(ReactNoop.flush()).toEqual(['Foo', 'Bar', 'Foo']);
696 + expect(ReactNoop.getChildren()).toEqual([
697 + span('Foo: 3'),
698 + span('Bar: 3'),
699 + span('Foo: 3'),
700 + ]);
701 + });
702 +
703 it('warns if calculateChangedBits returns larger than a 31-bit integer', () => {
704 spyOnDev(console, 'error');
705