@samitouri / QOS-React / commits / ec60457bcd

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 {