Popping context is O(1) in SSR (#13019)

This commit is contained in:
Dan Abramov 2018-06-11 20:52:39 +01:00 committed by GitHub
parent 30bc8ef792
commit ec60457bcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 48 additions and 27 deletions

View File

@ -641,8 +641,10 @@ class ReactDOMServerRenderer {
previousWasTextNode: boolean;
makeStaticMarkup: boolean;
providerStack: Array<?ReactProvider<any>>;
providerIndex: number;
contextIndex: number;
contextStack: Array<ReactContext<any>>;
contextValueStack: Array<any>;
contextProviderStack: ?Array<ReactProvider<any>>; // DEV-only
constructor(children: mixed, makeStaticMarkup: boolean) {
const flatChildren = flattenTopLevelChildren(children);
@ -667,46 +669,65 @@ class ReactDOMServerRenderer {
this.makeStaticMarkup = makeStaticMarkup;
// Context (new API)
this.providerStack = []; // Stack of provider objects
this.providerIndex = -1;
this.contextIndex = -1;
this.contextStack = [];
this.contextValueStack = [];
if (__DEV__) {
this.contextProviderStack = [];
}
}
/**
* Note: We use just two stacks regardless of how many context providers you have.
* Providers are always popped in the reverse order to how they were pushed
* so we always know on the way down which provider you'll encounter next on the way up.
* On the way down, we push the current provider, and its context value *before*
* we mutated it, onto the stacks. Therefore, on the way up, we always know which
* provider needs to be "restored" to which value.
* https://github.com/facebook/react/pull/12985#issuecomment-396301248
*/
pushProvider<T>(provider: ReactProvider<T>): void {
this.providerIndex += 1;
this.providerStack[this.providerIndex] = provider;
const index = ++this.contextIndex;
const context: ReactContext<any> = provider.type._context;
const previousValue = context._currentValue;
// Remember which value to restore this context to on our way up.
this.contextStack[index] = context;
this.contextValueStack[index] = previousValue;
if (__DEV__) {
// Only used for push/pop mismatch warnings.
(this.contextProviderStack: any)[index] = provider;
}
// Mutate the current value.
context._currentValue = provider.props.value;
}
popProvider<T>(provider: ReactProvider<T>): void {
const index = this.contextIndex;
if (__DEV__) {
warning(
this.providerIndex > -1 &&
provider === this.providerStack[this.providerIndex],
index > -1 && provider === (this.contextProviderStack: any)[index],
'Unexpected pop.',
);
}
this.providerStack[this.providerIndex] = null;
this.providerIndex -= 1;
const context: ReactContext<any> = provider.type._context;
// Find the closest parent provider of the same type and use its value.
// TODO: it would be nice to avoid this being O(N).
let contextPriorProvider = null;
for (let i = this.providerIndex; i >= 0; i--) {
// We assume this Flow type is correct because of the index check above
// and because pushProvider() enforces the correct type.
const priorProvider: ReactProvider<any> = (this.providerStack[i]: any);
if (priorProvider.type === provider.type) {
contextPriorProvider = priorProvider;
break;
}
}
if (contextPriorProvider !== null) {
context._currentValue = contextPriorProvider.props.value;
} else {
context._currentValue = context._defaultValue;
const context: ReactContext<any> = this.contextStack[index];
const previousValue = this.contextValueStack[index];
// "Hide" these null assignments from Flow by using `any`
// because conceptually they are deletions--as long as we
// promise to never access values beyond `this.contextIndex`.
this.contextStack[index] = (null: any);
this.contextValueStack[index] = (null: any);
if (__DEV__) {
(this.contextProviderStack: any)[index] = (null: any);
}
this.contextIndex--;
// Restore to the previous value we stored as we were walking down.
context._currentValue = previousValue;
}
read(bytes: number): string | null {