-
Notifications
You must be signed in to change notification settings - Fork 47.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New context API #11818
Merged
Merged
New context API #11818
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
1b9e621
New context API
acdlite 191ee70
Fuzz tester for context
acdlite 1dabc74
Use ReactElement for provider and consumer children
acdlite 2c9a906
Compare context values using Object.is
acdlite e3d66b3
Unify more branches in createFiberFromElement
acdlite 4baa415
Store providers on global stack
acdlite 63d366c
Add support for Provider and Consumer to server-side renderer
acdlite fc77b8d
Put new context API behind a feature flag
acdlite 59c67e9
Store nearest provider on context object
acdlite 7994d5a
Handle reentrancy in server renderer
acdlite bfebf36
Bailout of consumer updates using bitmask
acdlite f39333e
Store current value and changed bits on context object
acdlite b7fec05
Use maximum of 31 bits for bitmask
acdlite e67bfd8
ProviderComponent -> ContextProvider, ConsumerComponent -> ContextCon…
acdlite 79e289c
Inline Object.is
acdlite d2bd03a
Warn if multiple renderers concurrently render the same context provider
acdlite 0276eb5
Nits that came up during review
acdlite File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,11 @@ | |
*/ | ||
|
||
import type {ReactElement} from 'shared/ReactElementType'; | ||
import type { | ||
ReactProvider, | ||
ReactConsumer, | ||
ReactContext, | ||
} from 'shared/ReactTypes'; | ||
|
||
import React from 'react'; | ||
import emptyFunction from 'fbjs/lib/emptyFunction'; | ||
|
@@ -25,6 +30,8 @@ import { | |
REACT_CALL_TYPE, | ||
REACT_RETURN_TYPE, | ||
REACT_PORTAL_TYPE, | ||
REACT_PROVIDER_TYPE, | ||
REACT_CONTEXT_TYPE, | ||
} from 'shared/ReactSymbols'; | ||
|
||
import { | ||
|
@@ -192,7 +199,7 @@ function warnNoop( | |
const constructor = publicInstance.constructor; | ||
const componentName = | ||
(constructor && getComponentName(constructor)) || 'ReactClass'; | ||
const warningKey = `${componentName}.${callerName}`; | ||
const warningKey = componentName + '.' + callerName; | ||
if (didWarnAboutNoopUpdateForComponent[warningKey]) { | ||
return; | ||
} | ||
|
@@ -603,6 +610,7 @@ function resolve( | |
} | ||
|
||
type Frame = { | ||
type: mixed, | ||
domNamespace: string, | ||
children: FlatReactChildren, | ||
childIndex: number, | ||
|
@@ -622,12 +630,16 @@ class ReactDOMServerRenderer { | |
previousWasTextNode: boolean; | ||
makeStaticMarkup: boolean; | ||
|
||
providerStack: Array<?ReactProvider<any>>; | ||
providerIndex: number; | ||
|
||
constructor(children: mixed, makeStaticMarkup: boolean) { | ||
const flatChildren = flattenTopLevelChildren(children); | ||
|
||
const topFrame: Frame = { | ||
// Assume all trees start in the HTML namespace (not totally true, but | ||
// this is what we did historically) | ||
type: null, | ||
domNamespace: Namespaces.html, | ||
children: flatChildren, | ||
childIndex: 0, | ||
|
@@ -642,6 +654,39 @@ class ReactDOMServerRenderer { | |
this.currentSelectValue = null; | ||
this.previousWasTextNode = false; | ||
this.makeStaticMarkup = makeStaticMarkup; | ||
|
||
// Context (new API) | ||
this.providerStack = []; // Stack of provider objects | ||
this.providerIndex = -1; | ||
} | ||
|
||
pushProvider<T>(provider: ReactProvider<T>): void { | ||
this.providerIndex += 1; | ||
this.providerStack[this.providerIndex] = provider; | ||
const context: ReactContext<any> = provider.type.context; | ||
context.currentValue = provider.props.value; | ||
} | ||
|
||
popProvider<T>(provider: ReactProvider<T>): void { | ||
if (__DEV__) { | ||
warning( | ||
this.providerIndex > -1 && | ||
provider === this.providerStack[this.providerIndex], | ||
'Unexpected pop.', | ||
); | ||
} | ||
this.providerStack[this.providerIndex] = null; | ||
this.providerIndex -= 1; | ||
const context: ReactContext<any> = provider.type.context; | ||
if (this.providerIndex < 0) { | ||
context.currentValue = context.defaultValue; | ||
} else { | ||
// We assume this type is correct because of the index check above. | ||
const previousProvider: ReactProvider<any> = (this.providerStack[ | ||
this.providerIndex | ||
]: any); | ||
context.currentValue = previousProvider.props.value; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe there is a way to refactor this so you don't have almost the same implementation as in https://github.com/acdlite/react/blob/0276eb5c5b27ea6ac251c42bd83211c7d486ab16/packages/react-reconciler/src/ReactFiberNewContext.js |
||
|
||
read(bytes: number): string | null { | ||
|
@@ -663,8 +708,15 @@ class ReactDOMServerRenderer { | |
this.previousWasTextNode = false; | ||
} | ||
this.stack.pop(); | ||
if (frame.tag === 'select') { | ||
if (frame.type === 'select') { | ||
this.currentSelectValue = null; | ||
} else if ( | ||
frame.type != null && | ||
frame.type.type != null && | ||
frame.type.type.$$typeof === REACT_PROVIDER_TYPE | ||
) { | ||
const provider: ReactProvider<any> = (frame.type: any); | ||
this.popProvider(provider); | ||
} | ||
continue; | ||
} | ||
|
@@ -723,6 +775,7 @@ class ReactDOMServerRenderer { | |
} | ||
const nextChildren = toArray(nextChild); | ||
const frame: Frame = { | ||
type: null, | ||
domNamespace: parentNamespace, | ||
children: nextChildren, | ||
childIndex: 0, | ||
|
@@ -738,12 +791,18 @@ class ReactDOMServerRenderer { | |
// Safe because we just checked it's an element. | ||
const nextElement = ((nextChild: any): ReactElement); | ||
const elementType = nextElement.type; | ||
|
||
if (typeof elementType === 'string') { | ||
return this.renderDOM(nextElement, context, parentNamespace); | ||
} | ||
|
||
switch (elementType) { | ||
case REACT_FRAGMENT_TYPE: | ||
case REACT_FRAGMENT_TYPE: { | ||
const nextChildren = toArray( | ||
((nextChild: any): ReactElement).props.children, | ||
); | ||
const frame: Frame = { | ||
type: null, | ||
domNamespace: parentNamespace, | ||
children: nextChildren, | ||
childIndex: 0, | ||
|
@@ -755,6 +814,7 @@ class ReactDOMServerRenderer { | |
} | ||
this.stack.push(frame); | ||
return ''; | ||
} | ||
case REACT_CALL_TYPE: | ||
case REACT_RETURN_TYPE: | ||
invariant( | ||
|
@@ -764,8 +824,62 @@ class ReactDOMServerRenderer { | |
); | ||
// eslint-disable-next-line-no-fallthrough | ||
default: | ||
return this.renderDOM(nextElement, context, parentNamespace); | ||
break; | ||
} | ||
if (typeof elementType === 'object' && elementType !== null) { | ||
switch (elementType.$$typeof) { | ||
case REACT_PROVIDER_TYPE: { | ||
const provider: ReactProvider<any> = (nextChild: any); | ||
const nextProps = provider.props; | ||
const nextChildren = toArray(nextProps.children); | ||
const frame: Frame = { | ||
type: provider, | ||
domNamespace: parentNamespace, | ||
children: nextChildren, | ||
childIndex: 0, | ||
context: context, | ||
footer: '', | ||
}; | ||
if (__DEV__) { | ||
((frame: any): FrameDev).debugElementStack = []; | ||
artzhookov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
this.pushProvider(provider); | ||
|
||
this.stack.push(frame); | ||
return ''; | ||
} | ||
case REACT_CONTEXT_TYPE: { | ||
const consumer: ReactConsumer<any> = (nextChild: any); | ||
const nextProps = consumer.props; | ||
const nextValue = consumer.type.currentValue; | ||
|
||
const nextChildren = toArray(nextProps.render(nextValue)); | ||
const frame: Frame = { | ||
type: nextChild, | ||
domNamespace: parentNamespace, | ||
children: nextChildren, | ||
childIndex: 0, | ||
context: context, | ||
footer: '', | ||
}; | ||
if (__DEV__) { | ||
((frame: any): FrameDev).debugElementStack = []; | ||
} | ||
this.stack.push(frame); | ||
return ''; | ||
} | ||
default: | ||
break; | ||
} | ||
} | ||
invariant( | ||
false, | ||
'Element type is invalid: expected a string (for built-in ' + | ||
'components) or a class/function (for composite components) ' + | ||
'but got: %s.', | ||
elementType == null ? elementType : typeof elementType, | ||
); | ||
} | ||
} | ||
|
||
|
@@ -1052,7 +1166,7 @@ class ReactDOMServerRenderer { | |
} | ||
const frame = { | ||
domNamespace: getChildNamespace(parentNamespace, element.type), | ||
tag, | ||
type: tag, | ||
children, | ||
childIndex: 0, | ||
context: context, | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is because the previous version was screwing up my editor's syntax highlighting 😆
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like the worst possible reason for a change in a project with more than one developer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can think of worse ones