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