Popping context is O(1) in SSR (#13019)
Dan Abramov committed
Jun 11, 2018 at 20:52 UTC
ec60457bcd22d06c404a44839c14beda88081ac7
1 file changed
+48
-27
packages/react-dom/src/server/ReactPartialRenderer.js
+48
-27
@@ -641,8 +641,10 @@ class ReactDOMServerRenderer {
641
previousWasTextNode: boolean;
642
makeStaticMarkup: boolean;
643
644
- providerStack: Array<?ReactProvider<any>>;
645
- providerIndex: number;
644
+ contextIndex: number;
645
+ contextStack: Array<ReactContext<any>>;
646
+ contextValueStack: Array<any>;
647
+ contextProviderStack: ?Array<ReactProvider<any>>; // DEV-only
648
649
constructor(children: mixed, makeStaticMarkup: boolean) {
650
const flatChildren = flattenTopLevelChildren(children);
@@ -667,46 +669,65 @@ class ReactDOMServerRenderer {
669
this.makeStaticMarkup = makeStaticMarkup;
670
671
// Context (new API)
670
- this.providerStack = []; // Stack of provider objects
671
- this.providerIndex = -1;
672
+ this.contextIndex = -1;
673
+ this.contextStack = [];
674
+ this.contextValueStack = [];
675
+ if (__DEV__) {
676
+ this.contextProviderStack = [];
677
+ }
678
}
679
680
+ /**
681
+ * Note: We use just two stacks regardless of how many context providers you have.
682
+ * Providers are always popped in the reverse order to how they were pushed
683
+ * so we always know on the way down which provider you'll encounter next on the way up.
684
+ * On the way down, we push the current provider, and its context value *before*
685
+ * we mutated it, onto the stacks. Therefore, on the way up, we always know which
686
+ * provider needs to be "restored" to which value.
687
+ * https://github.com/facebook/react/pull/12985#issuecomment-396301248
688
+ */
689
+
690
pushProvider<T>(provider: ReactProvider<T>): void {
675
- this.providerIndex += 1;
676
- this.providerStack[this.providerIndex] = provider;
691
+ const index = ++this.contextIndex;
692
const context: ReactContext<any> = provider.type._context;
693
+ const previousValue = context._currentValue;
694
+
695
+ // Remember which value to restore this context to on our way up.
696
+ this.contextStack[index] = context;
697
+ this.contextValueStack[index] = previousValue;
698
+ if (__DEV__) {
699
+ // Only used for push/pop mismatch warnings.
700
+ (this.contextProviderStack: any)[index] = provider;
701
+ }
702
+
703
+ // Mutate the current value.
704
context._currentValue = provider.props.value;
705
}
706
707
popProvider<T>(provider: ReactProvider<T>): void {
708
+ const index = this.contextIndex;
709
if (__DEV__) {
710
warning(
684
- this.providerIndex > -1 &&
685
- provider === this.providerStack[this.providerIndex],
711
+ index > -1 && provider === (this.contextProviderStack: any)[index],
712
'Unexpected pop.',
713
);
714
}
689
- this.providerStack[this.providerIndex] = null;
690
- this.providerIndex -= 1;
691
- const context: ReactContext<any> = provider.type._context;
715
693
- // Find the closest parent provider of the same type and use its value.
694
- // TODO: it would be nice to avoid this being O(N).
695
- let contextPriorProvider = null;
696
- for (let i = this.providerIndex; i >= 0; i--) {
697
- // We assume this Flow type is correct because of the index check above
698
- // and because pushProvider() enforces the correct type.
699
- const priorProvider: ReactProvider<any> = (this.providerStack[i]: any);
700
- if (priorProvider.type === provider.type) {
701
- contextPriorProvider = priorProvider;
702
- break;
703
- }
704
- }
705
- if (contextPriorProvider !== null) {
706
- context._currentValue = contextPriorProvider.props.value;
707
- } else {
708
- context._currentValue = context._defaultValue;
716
+ const context: ReactContext<any> = this.contextStack[index];
717
+ const previousValue = this.contextValueStack[index];
718
+
719
+ // "Hide" these null assignments from Flow by using `any`
720
+ // because conceptually they are deletions--as long as we
721
+ // promise to never access values beyond `this.contextIndex`.
722
+ this.contextStack[index] = (null: any);
723
+ this.contextValueStack[index] = (null: any);
724
+ if (__DEV__) {
725
+ (this.contextProviderStack: any)[index] = (null: any);
726
}
727
+ this.contextIndex--;
728
+
729
+ // Restore to the previous value we stored as we were walking down.
730
+ context._currentValue = previousValue;
731
}
732
733
read(bytes: number): string | null {