Skip to content

Commit ec60457

Browse files
authored
Popping context is O(1) in SSR (#13019)
1 parent 30bc8ef commit ec60457

File tree

1 file changed

+48
-27
lines changed

1 file changed

+48
-27
lines changed

packages/react-dom/src/server/ReactPartialRenderer.js

+48-27
Original file line numberDiff line numberDiff line change
@@ -641,8 +641,10 @@ class ReactDOMServerRenderer {
641641
previousWasTextNode: boolean;
642642
makeStaticMarkup: boolean;
643643

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
646648

647649
constructor(children: mixed, makeStaticMarkup: boolean) {
648650
const flatChildren = flattenTopLevelChildren(children);
@@ -667,46 +669,65 @@ class ReactDOMServerRenderer {
667669
this.makeStaticMarkup = makeStaticMarkup;
668670

669671
// 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+
}
672678
}
673679

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+
674690
pushProvider<T>(provider: ReactProvider<T>): void {
675-
this.providerIndex += 1;
676-
this.providerStack[this.providerIndex] = provider;
691+
const index = ++this.contextIndex;
677692
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.
678704
context._currentValue = provider.props.value;
679705
}
680706

681707
popProvider<T>(provider: ReactProvider<T>): void {
708+
const index = this.contextIndex;
682709
if (__DEV__) {
683710
warning(
684-
this.providerIndex > -1 &&
685-
provider === this.providerStack[this.providerIndex],
711+
index > -1 && provider === (this.contextProviderStack: any)[index],
686712
'Unexpected pop.',
687713
);
688714
}
689-
this.providerStack[this.providerIndex] = null;
690-
this.providerIndex -= 1;
691-
const context: ReactContext<any> = provider.type._context;
692715

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);
709726
}
727+
this.contextIndex--;
728+
729+
// Restore to the previous value we stored as we were walking down.
730+
context._currentValue = previousValue;
710731
}
711732

712733
read(bytes: number): string | null {

0 commit comments

Comments
 (0)