[Fizz] Implement Classes (#21200)

* Legacy context

* Port Classes from Fiber to Fizz
This commit is contained in:
Sebastian Markbåge 2021-04-08 13:42:37 -04:00 committed by GitHub
parent 75c616554d
commit cf45a623a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 1042 additions and 13 deletions

View File

@ -0,0 +1,673 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {emptyContextObject} from './ReactFizzContext';
import {disableLegacyContext} from 'shared/ReactFeatureFlags';
import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
import isArray from 'shared/isArray';
const didWarnAboutNoopUpdateForComponent = {};
let didWarnAboutUninitializedState;
let didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
let didWarnAboutLegacyLifecyclesAndDerivedState;
let didWarnAboutUndefinedDerivedState;
let warnOnUndefinedDerivedState;
let warnOnInvalidCallback;
let didWarnAboutDirectlyAssigningPropsToState;
let didWarnAboutContextTypeAndContextTypes;
let didWarnAboutInvalidateContextType;
if (__DEV__) {
didWarnAboutUninitializedState = new Set();
didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate = new Set();
didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
didWarnAboutDirectlyAssigningPropsToState = new Set();
didWarnAboutUndefinedDerivedState = new Set();
didWarnAboutContextTypeAndContextTypes = new Set();
didWarnAboutInvalidateContextType = new Set();
const didWarnOnInvalidCallback = new Set();
warnOnInvalidCallback = function(callback: mixed, callerName: string) {
if (callback === null || typeof callback === 'function') {
return;
}
const key = callerName + '_' + (callback: any);
if (!didWarnOnInvalidCallback.has(key)) {
didWarnOnInvalidCallback.add(key);
console.error(
'%s(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callerName,
callback,
);
}
};
warnOnUndefinedDerivedState = function(type, partialState) {
if (partialState === undefined) {
const componentName = getComponentNameFromType(type) || 'Component';
if (!didWarnAboutUndefinedDerivedState.has(componentName)) {
didWarnAboutUndefinedDerivedState.add(componentName);
console.error(
'%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
'You have returned undefined.',
componentName,
);
}
}
};
}
function warnNoop(
publicInstance: React$Component<any, any>,
callerName: string,
) {
if (__DEV__) {
const constructor = publicInstance.constructor;
const componentName =
(constructor && getComponentNameFromType(constructor)) || 'ReactClass';
const warningKey = componentName + '.' + callerName;
if (didWarnAboutNoopUpdateForComponent[warningKey]) {
return;
}
console.error(
'%s(...): Can only update a mounting component. ' +
'This usually means you called %s() outside componentWillMount() on the server. ' +
'This is a no-op.\n\nPlease check the code for the %s component.',
callerName,
callerName,
componentName,
);
didWarnAboutNoopUpdateForComponent[warningKey] = true;
}
}
type InternalInstance = {
queue: null | Array<Object>,
replace: boolean,
};
const classComponentUpdater = {
isMounted(inst) {
return false;
},
enqueueSetState(inst, payload, callback) {
const internals: InternalInstance = getInstance(inst);
if (internals.queue === null) {
warnNoop(inst, 'setState');
} else {
internals.queue.push(payload);
if (__DEV__) {
if (callback !== undefined && callback !== null) {
warnOnInvalidCallback(callback, 'setState');
}
}
}
},
enqueueReplaceState(inst, payload, callback) {
const internals: InternalInstance = getInstance(inst);
internals.replace = true;
internals.queue = [payload];
if (__DEV__) {
if (callback !== undefined && callback !== null) {
warnOnInvalidCallback(callback, 'setState');
}
}
},
enqueueForceUpdate(inst, callback) {
const internals: InternalInstance = getInstance(inst);
if (internals.queue === null) {
warnNoop(inst, 'forceUpdate');
} else {
if (__DEV__) {
if (callback !== undefined && callback !== null) {
warnOnInvalidCallback(callback, 'setState');
}
}
}
},
};
function applyDerivedStateFromProps(
instance: any,
ctor: any,
getDerivedStateFromProps: (props: any, state: any) => any,
prevState: any,
nextProps: any,
) {
const partialState = getDerivedStateFromProps(nextProps, prevState);
if (__DEV__) {
warnOnUndefinedDerivedState(ctor, partialState);
}
// Merge the partial state and the previous state.
const newState =
partialState === null || partialState === undefined
? prevState
: Object.assign({}, prevState, partialState);
return newState;
}
export function constructClassInstance(
ctor: any,
props: any,
maskedLegacyContext: any,
): any {
let context = emptyContextObject;
const contextType = ctor.contextType;
if (__DEV__) {
if ('contextType' in ctor) {
const isValid =
// Allow null for conditional declaration
contextType === null ||
(contextType !== undefined &&
contextType.$$typeof === REACT_CONTEXT_TYPE &&
contextType._context === undefined); // Not a <Context.Consumer>
if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
didWarnAboutInvalidateContextType.add(ctor);
let addendum = '';
if (contextType === undefined) {
addendum =
' However, it is set to undefined. ' +
'This can be caused by a typo or by mixing up named and default imports. ' +
'This can also happen due to a circular dependency, so ' +
'try moving the createContext() call to a separate file.';
} else if (typeof contextType !== 'object') {
addendum = ' However, it is set to a ' + typeof contextType + '.';
} else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
addendum = ' Did you accidentally pass the Context.Provider instead?';
} else if (contextType._context !== undefined) {
// <Context.Consumer>
addendum = ' Did you accidentally pass the Context.Consumer instead?';
} else {
addendum =
' However, it is set to an object with keys {' +
Object.keys(contextType).join(', ') +
'}.';
}
console.error(
'%s defines an invalid contextType. ' +
'contextType should point to the Context object returned by React.createContext().%s',
getComponentNameFromType(ctor) || 'Component',
addendum,
);
}
}
}
if (typeof contextType === 'object' && contextType !== null) {
// TODO: Implement Context.
// context = readContext((contextType: any));
throw new Error('Context is not yet implemented.');
} else if (!disableLegacyContext) {
context = maskedLegacyContext;
}
const instance = new ctor(props, context);
if (__DEV__) {
if (
typeof ctor.getDerivedStateFromProps === 'function' &&
(instance.state === null || instance.state === undefined)
) {
const componentName = getComponentNameFromType(ctor) || 'Component';
if (!didWarnAboutUninitializedState.has(componentName)) {
didWarnAboutUninitializedState.add(componentName);
console.error(
'`%s` uses `getDerivedStateFromProps` but its initial state is ' +
'%s. This is not recommended. Instead, define the initial state by ' +
'assigning an object to `this.state` in the constructor of `%s`. ' +
'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
componentName,
instance.state === null ? 'null' : 'undefined',
componentName,
);
}
}
// If new component APIs are defined, "unsafe" lifecycles won't be called.
// Warn about these lifecycles if they are present.
// Don't warn about react-lifecycles-compat polyfilled methods though.
if (
typeof ctor.getDerivedStateFromProps === 'function' ||
typeof instance.getSnapshotBeforeUpdate === 'function'
) {
let foundWillMountName = null;
let foundWillReceivePropsName = null;
let foundWillUpdateName = null;
if (
typeof instance.componentWillMount === 'function' &&
instance.componentWillMount.__suppressDeprecationWarning !== true
) {
foundWillMountName = 'componentWillMount';
} else if (typeof instance.UNSAFE_componentWillMount === 'function') {
foundWillMountName = 'UNSAFE_componentWillMount';
}
if (
typeof instance.componentWillReceiveProps === 'function' &&
instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
) {
foundWillReceivePropsName = 'componentWillReceiveProps';
} else if (
typeof instance.UNSAFE_componentWillReceiveProps === 'function'
) {
foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
}
if (
typeof instance.componentWillUpdate === 'function' &&
instance.componentWillUpdate.__suppressDeprecationWarning !== true
) {
foundWillUpdateName = 'componentWillUpdate';
} else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
foundWillUpdateName = 'UNSAFE_componentWillUpdate';
}
if (
foundWillMountName !== null ||
foundWillReceivePropsName !== null ||
foundWillUpdateName !== null
) {
const componentName = getComponentNameFromType(ctor) || 'Component';
const newApiName =
typeof ctor.getDerivedStateFromProps === 'function'
? 'getDerivedStateFromProps()'
: 'getSnapshotBeforeUpdate()';
if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(componentName)) {
didWarnAboutLegacyLifecyclesAndDerivedState.add(componentName);
console.error(
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
'%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://reactjs.org/link/unsafe-component-lifecycles',
componentName,
newApiName,
foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
foundWillReceivePropsName !== null
? `\n ${foundWillReceivePropsName}`
: '',
foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
);
}
}
}
}
return instance;
}
function checkClassInstance(instance: any, ctor: any, newProps: any) {
if (__DEV__) {
const name = getComponentNameFromType(ctor) || 'Component';
const renderPresent = instance.render;
if (!renderPresent) {
if (ctor.prototype && typeof ctor.prototype.render === 'function') {
console.error(
'%s(...): No `render` method found on the returned component ' +
'instance: did you accidentally return an object from the constructor?',
name,
);
} else {
console.error(
'%s(...): No `render` method found on the returned component ' +
'instance: you may have forgotten to define `render`.',
name,
);
}
}
if (
instance.getInitialState &&
!instance.getInitialState.isReactClassApproved &&
!instance.state
) {
console.error(
'getInitialState was defined on %s, a plain JavaScript class. ' +
'This is only supported for classes created using React.createClass. ' +
'Did you mean to define a state property instead?',
name,
);
}
if (
instance.getDefaultProps &&
!instance.getDefaultProps.isReactClassApproved
) {
console.error(
'getDefaultProps was defined on %s, a plain JavaScript class. ' +
'This is only supported for classes created using React.createClass. ' +
'Use a static property to define defaultProps instead.',
name,
);
}
if (instance.propTypes) {
console.error(
'propTypes was defined as an instance property on %s. Use a static ' +
'property to define propTypes instead.',
name,
);
}
if (instance.contextType) {
console.error(
'contextType was defined as an instance property on %s. Use a static ' +
'property to define contextType instead.',
name,
);
}
if (disableLegacyContext) {
if (ctor.childContextTypes) {
console.error(
'%s uses the legacy childContextTypes API which is no longer supported. ' +
'Use React.createContext() instead.',
name,
);
}
if (ctor.contextTypes) {
console.error(
'%s uses the legacy contextTypes API which is no longer supported. ' +
'Use React.createContext() with static contextType instead.',
name,
);
}
} else {
if (instance.contextTypes) {
console.error(
'contextTypes was defined as an instance property on %s. Use a static ' +
'property to define contextTypes instead.',
name,
);
}
if (
ctor.contextType &&
ctor.contextTypes &&
!didWarnAboutContextTypeAndContextTypes.has(ctor)
) {
didWarnAboutContextTypeAndContextTypes.add(ctor);
console.error(
'%s declares both contextTypes and contextType static properties. ' +
'The legacy contextTypes property will be ignored.',
name,
);
}
}
if (typeof instance.componentShouldUpdate === 'function') {
console.error(
'%s has a method called ' +
'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
'The name is phrased as a question because the function is ' +
'expected to return a value.',
name,
);
}
if (
ctor.prototype &&
ctor.prototype.isPureReactComponent &&
typeof instance.shouldComponentUpdate !== 'undefined'
) {
console.error(
'%s has a method called shouldComponentUpdate(). ' +
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
'Please extend React.Component if shouldComponentUpdate is used.',
getComponentNameFromType(ctor) || 'A pure component',
);
}
if (typeof instance.componentDidUnmount === 'function') {
console.error(
'%s has a method called ' +
'componentDidUnmount(). But there is no such lifecycle method. ' +
'Did you mean componentWillUnmount()?',
name,
);
}
if (typeof instance.componentDidReceiveProps === 'function') {
console.error(
'%s has a method called ' +
'componentDidReceiveProps(). But there is no such lifecycle method. ' +
'If you meant to update the state in response to changing props, ' +
'use componentWillReceiveProps(). If you meant to fetch data or ' +
'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
name,
);
}
if (typeof instance.componentWillRecieveProps === 'function') {
console.error(
'%s has a method called ' +
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
name,
);
}
if (typeof instance.UNSAFE_componentWillRecieveProps === 'function') {
console.error(
'%s has a method called ' +
'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?',
name,
);
}
const hasMutatedProps = instance.props !== newProps;
if (instance.props !== undefined && hasMutatedProps) {
console.error(
'%s(...): When calling super() in `%s`, make sure to pass ' +
"up the same props that your component's constructor was passed.",
name,
name,
);
}
if (instance.defaultProps) {
console.error(
'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
' Instead, define defaultProps as a static property on %s.',
name,
name,
);
}
if (
typeof instance.getSnapshotBeforeUpdate === 'function' &&
typeof instance.componentDidUpdate !== 'function' &&
!didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(ctor)
) {
didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(ctor);
console.error(
'%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
'This component defines getSnapshotBeforeUpdate() only.',
getComponentNameFromType(ctor),
);
}
if (typeof instance.getDerivedStateFromProps === 'function') {
console.error(
'%s: getDerivedStateFromProps() is defined as an instance method ' +
'and will be ignored. Instead, declare it as a static method.',
name,
);
}
if (typeof instance.getDerivedStateFromError === 'function') {
console.error(
'%s: getDerivedStateFromError() is defined as an instance method ' +
'and will be ignored. Instead, declare it as a static method.',
name,
);
}
if (typeof ctor.getSnapshotBeforeUpdate === 'function') {
console.error(
'%s: getSnapshotBeforeUpdate() is defined as a static method ' +
'and will be ignored. Instead, declare it as an instance method.',
name,
);
}
const state = instance.state;
if (state && (typeof state !== 'object' || isArray(state))) {
console.error('%s.state: must be set to an object or null', name);
}
if (
typeof instance.getChildContext === 'function' &&
typeof ctor.childContextTypes !== 'object'
) {
console.error(
'%s.getChildContext(): childContextTypes must be defined in order to ' +
'use getChildContext().',
name,
);
}
}
}
function callComponentWillMount(type, instance) {
const oldState = instance.state;
if (typeof instance.componentWillMount === 'function') {
instance.componentWillMount();
}
if (typeof instance.UNSAFE_componentWillMount === 'function') {
instance.UNSAFE_componentWillMount();
}
if (oldState !== instance.state) {
if (__DEV__) {
console.error(
'%s.componentWillMount(): Assigning directly to this.state is ' +
"deprecated (except inside a component's " +
'constructor). Use setState instead.',
getComponentNameFromType(type) || 'Component',
);
}
classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
}
}
function processUpdateQueue(
internalInstance: InternalInstance,
inst: any,
props: any,
maskedLegacyContext: any,
): void {
if (internalInstance.queue !== null && internalInstance.queue.length > 0) {
const oldQueue = internalInstance.queue;
const oldReplace = internalInstance.replace;
internalInstance.queue = null;
internalInstance.replace = false;
if (oldReplace && oldQueue.length === 1) {
inst.state = oldQueue[0];
} else {
let nextState = oldReplace ? oldQueue[0] : inst.state;
let dontMutate = true;
for (let i = oldReplace ? 1 : 0; i < oldQueue.length; i++) {
const partial = oldQueue[i];
const partialState =
typeof partial === 'function'
? partial.call(inst, nextState, props, maskedLegacyContext)
: partial;
if (partialState != null) {
if (dontMutate) {
dontMutate = false;
nextState = Object.assign({}, nextState, partialState);
} else {
Object.assign(nextState, partialState);
}
}
}
inst.state = nextState;
}
} else {
internalInstance.queue = null;
}
}
// Invokes the mount life-cycles on a previously never rendered instance.
export function mountClassInstance(
instance: any,
ctor: any,
newProps: any,
maskedLegacyContext: any,
): void {
if (__DEV__) {
checkClassInstance(instance, ctor, newProps);
}
const initialState = instance.state !== undefined ? instance.state : null;
instance.updater = classComponentUpdater;
instance.props = newProps;
instance.state = initialState;
// We don't bother initializing the refs object on the server, since we're not going to resolve them anyway.
// The internal instance will be used to manage updates that happen during this mount.
const internalInstance: InternalInstance = {
queue: null,
replace: false,
};
setInstance(instance, internalInstance);
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
// TODO: Implement Context.
// instance.context = readContext(contextType);
throw new Error('Context is not yet implemented.');
} else if (disableLegacyContext) {
instance.context = emptyContextObject;
} else {
instance.context = maskedLegacyContext;
}
if (__DEV__) {
if (instance.state === newProps) {
const componentName = getComponentNameFromType(ctor) || 'Component';
if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) {
didWarnAboutDirectlyAssigningPropsToState.add(componentName);
console.error(
'%s: It is not recommended to assign props directly to state ' +
"because updates to props won't be reflected in state. " +
'In most cases, it is better to use props directly.',
componentName,
);
}
}
}
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
instance.state = applyDerivedStateFromProps(
instance,
ctor,
getDerivedStateFromProps,
initialState,
newProps,
);
}
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
callComponentWillMount(ctor, instance);
// If we had additional state updates during this life-cycle, let's
// process them now.
processUpdateQueue(
internalInstance,
instance,
newProps,
maskedLegacyContext,
);
}
}

View File

@ -0,0 +1,94 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {disableLegacyContext} from 'shared/ReactFeatureFlags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import invariant from 'shared/invariant';
import checkPropTypes from 'shared/checkPropTypes';
let warnedAboutMissingGetChildContext;
if (__DEV__) {
warnedAboutMissingGetChildContext = {};
}
export const emptyContextObject = {};
if (__DEV__) {
Object.freeze(emptyContextObject);
}
export function getMaskedContext(type: any, unmaskedContext: Object): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyContextObject;
}
const context = {};
for (const key in contextTypes) {
context[key] = unmaskedContext[key];
}
if (__DEV__) {
const name = getComponentNameFromType(type) || 'Unknown';
checkPropTypes(contextTypes, context, 'context', name);
}
return context;
}
}
export function processChildContext(
type: any,
instance: any,
parentContext: Object,
childContextTypes: Object,
): Object {
if (disableLegacyContext) {
return parentContext;
} else {
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
if (typeof instance.getChildContext !== 'function') {
if (__DEV__) {
const componentName = getComponentNameFromType(type) || 'Unknown';
if (!warnedAboutMissingGetChildContext[componentName]) {
warnedAboutMissingGetChildContext[componentName] = true;
console.error(
'%s.childContextTypes is specified but there is no getChildContext() method ' +
'on the instance. You can either define getChildContext() on %s or remove ' +
'childContextTypes from it.',
componentName,
componentName,
);
}
}
return parentContext;
}
const childContext = instance.getChildContext();
for (const contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentNameFromType(type) || 'Unknown',
contextKey,
);
}
if (__DEV__) {
const name = getComponentNameFromType(type) || 'Unknown';
checkPropTypes(childContextTypes, childContext, 'child context', name);
}
return {...parentContext, ...childContext};
}
}

View File

@ -47,14 +47,33 @@ import {
createSuspenseBoundaryID,
getChildFormatContext,
} from './ReactServerFormatConfig';
import {
constructClassInstance,
mountClassInstance,
} from './ReactFizzClassComponent';
import {
getMaskedContext,
processChildContext,
emptyContextObject,
} from './ReactFizzContext';
import {REACT_ELEMENT_TYPE, REACT_SUSPENSE_TYPE} from 'shared/ReactSymbols';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
disableLegacyContext,
disableModulePatternComponents,
warnAboutDefaultPropsOnFunctionComponents,
} from 'shared/ReactFeatureFlags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import invariant from 'shared/invariant';
import isArray from 'shared/isArray';
const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
type LegacyContext = {
[key: string]: any,
};
type SuspenseBoundary = {
+id: SuspenseBoundaryID,
rootSegmentID: number,
@ -72,6 +91,7 @@ type Task = {
blockedBoundary: Root | SuspenseBoundary,
blockedSegment: Segment, // the segment we'll write to
abortSet: Set<Task>, // the abortable set that this task belongs to
legacyContext: LegacyContext, // the current legacy context that this task is executing in
assignID: null | SuspenseBoundaryID, // id to assign to the content
};
@ -151,7 +171,7 @@ export function createRequest(
children: ReactNodeList,
destination: Destination,
responseState: ResponseState,
rootContext: FormatContext,
rootFormatContext: FormatContext,
progressiveChunkSize: number = DEFAULT_PROGRESSIVE_CHUNK_SIZE,
onError: (error: mixed) => void = defaultErrorHandler,
onCompleteAll: () => void = noop,
@ -178,7 +198,7 @@ export function createRequest(
onReadyToStream,
};
// This segment represents the root fallback.
const rootSegment = createPendingSegment(request, 0, null, rootContext);
const rootSegment = createPendingSegment(request, 0, null, rootFormatContext);
// There is no parent so conceptually, we're unblocked to flush this segment.
rootSegment.parentFlushed = true;
const rootTask = createTask(
@ -187,6 +207,7 @@ export function createRequest(
null,
rootSegment,
abortSet,
emptyContextObject,
null,
);
pingedTasks.push(rootTask);
@ -223,6 +244,7 @@ function createTask(
blockedBoundary: Root | SuspenseBoundary,
blockedSegment: Segment,
abortSet: Set<Task>,
legacyContext: LegacyContext,
assignID: null | SuspenseBoundaryID,
): Task {
request.allPendingTasks++;
@ -237,6 +259,7 @@ function createTask(
blockedBoundary,
blockedSegment,
abortSet,
legacyContext,
assignID,
};
abortSet.add(task);
@ -350,7 +373,7 @@ function renderSuspenseBoundary(
task.blockedSegment = parentSegment;
}
// We create suspended task for the fallback because we don't want to actually task
// We create suspended task for the fallback because we don't want to actually work
// on it yet in case we finish the main content, so we queue for later.
const suspendedFallbackTask = createTask(
request,
@ -358,9 +381,10 @@ function renderSuspenseBoundary(
parentBoundary,
boundarySegment,
fallbackAbortSet,
task.legacyContext,
newBoundary.id, // This is the ID we want to give this fallback so we can replace it later.
);
// TODO: This should be queued at a separate lower priority queue so that we only task
// TODO: This should be queued at a separate lower priority queue so that we only work
// on preparing fallbacks if we don't have any more main content to task on.
request.pingedTasks.push(suspendedFallbackTask);
}
@ -393,16 +417,247 @@ function renderHostElement(
pushEndInstance(segment.chunks, type, props);
}
function renderFunctionComponent(
function shouldConstruct(Component) {
return Component.prototype && Component.prototype.isReactComponent;
}
function renderWithHooks<Props, SecondArg>(
request: Request,
task: Task,
type: (props: any) => ReactNodeList,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
): any {
// TODO: Set up Hooks etc.
const children = Component(props, secondArg);
return children;
}
function finishClassComponent(
request: Request,
task: Task,
instance: Object,
Component: any,
props: any,
): ReactNodeList {
const nextChildren = instance.render();
if (__DEV__) {
if (instance.props !== props) {
if (!didWarnAboutReassigningProps) {
console.error(
'It looks like %s is reassigning its own `this.props` while rendering. ' +
'This is not supported and can lead to confusing bugs.',
getComponentNameFromType(Component) || 'a component',
);
}
didWarnAboutReassigningProps = true;
}
}
if (!disableLegacyContext) {
const childContextTypes = Component.childContextTypes;
if (childContextTypes !== null && childContextTypes !== undefined) {
const previousContext = task.legacyContext;
const mergedContext = processChildContext(
instance,
Component,
previousContext,
childContextTypes,
);
task.legacyContext = mergedContext;
renderNodeDestructive(request, task, nextChildren);
task.legacyContext = previousContext;
return;
}
}
renderNodeDestructive(request, task, nextChildren);
}
function renderClassComponent(
request: Request,
task: Task,
Component: any,
props: any,
): void {
const result = type(props);
// We're now successfully past this task, and we don't have to pop back to
// the previous task every again, so we can use the destructive recursive form.
renderNodeDestructive(request, task, result);
const unmaskedContext = !disableLegacyContext
? task.legacyContext
: undefined;
const instance = constructClassInstance(Component, props, unmaskedContext);
mountClassInstance(instance, Component, props, unmaskedContext);
finishClassComponent(request, task, Component);
}
const didWarnAboutBadClass = {};
const didWarnAboutModulePatternComponent = {};
const didWarnAboutContextTypeOnFunctionComponent = {};
const didWarnAboutGetDerivedStateOnFunctionComponent = {};
let didWarnAboutReassigningProps = false;
const didWarnAboutDefaultPropsOnFunctionComponent = {};
// This would typically be a function component but we still support module pattern
// components for some reason.
function renderIndeterminateComponent(
request: Request,
task: Task,
Component: any,
props: any,
): void {
let legacyContext;
if (!disableLegacyContext) {
legacyContext = getMaskedContext(Component, task.legacyContext);
}
if (__DEV__) {
if (
Component.prototype &&
typeof Component.prototype.render === 'function'
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutBadClass[componentName]) {
console.error(
"The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
'This is likely to cause errors. Change %s to extend React.Component instead.',
componentName,
componentName,
);
didWarnAboutBadClass[componentName] = true;
}
}
}
const value = renderWithHooks(request, task, Component, props, legacyContext);
if (__DEV__) {
// Support for module components is deprecated and is removed behind a flag.
// Whether or not it would crash later, we want to show a good message in DEV first.
if (
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function' &&
value.$$typeof === undefined
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutModulePatternComponent[componentName]) {
console.error(
'The <%s /> component appears to be a function component that returns a class instance. ' +
'Change %s to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
'cannot be called with `new` by React.',
componentName,
componentName,
componentName,
);
didWarnAboutModulePatternComponent[componentName] = true;
}
}
}
if (
// Run these checks in production only if the flag is off.
// Eventually we'll delete this branch altogether.
!disableModulePatternComponents &&
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function' &&
value.$$typeof === undefined
) {
if (__DEV__) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutModulePatternComponent[componentName]) {
console.error(
'The <%s /> component appears to be a function component that returns a class instance. ' +
'Change %s to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
'cannot be called with `new` by React.',
componentName,
componentName,
componentName,
);
didWarnAboutModulePatternComponent[componentName] = true;
}
}
mountClassInstance(value, Component, props, legacyContext);
finishClassComponent(request, task, value, Component);
} else {
// Proceed under the assumption that this is a function component
if (__DEV__) {
if (disableLegacyContext && Component.contextTypes) {
console.error(
'%s uses the legacy contextTypes API which is no longer supported. ' +
'Use React.createContext() with React.useContext() instead.',
getComponentNameFromType(Component) || 'Unknown',
);
}
}
if (__DEV__) {
validateFunctionComponentInDev(Component);
}
// We're now successfully past this task, and we don't have to pop back to
// the previous task every again, so we can use the destructive recursive form.
renderNodeDestructive(request, task, value);
}
}
function validateFunctionComponentInDev(Component: any): void {
if (__DEV__) {
if (Component) {
if (Component.childContextTypes) {
console.error(
'%s(...): childContextTypes cannot be defined on a function component.',
Component.displayName || Component.name || 'Component',
);
}
}
if (
warnAboutDefaultPropsOnFunctionComponents &&
Component.defaultProps !== undefined
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
console.error(
'%s: Support for defaultProps will be removed from function components ' +
'in a future major release. Use JavaScript default parameters instead.',
componentName,
);
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
}
}
if (typeof Component.getDerivedStateFromProps === 'function') {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
console.error(
'%s: Function components do not support getDerivedStateFromProps.',
componentName,
);
didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
}
}
if (
typeof Component.contextType === 'object' &&
Component.contextType !== null
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
console.error(
'%s: Function components do not support contextType.',
componentName,
);
didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
}
}
}
}
function renderElement(
@ -413,7 +668,11 @@ function renderElement(
node: ReactNodeList,
): void {
if (typeof type === 'function') {
renderFunctionComponent(request, task, type, props);
if (shouldConstruct(type)) {
renderClassComponent(request, task, type, props);
} else {
renderIndeterminateComponent(request, task, type, props);
}
} else if (typeof type === 'string') {
renderHostElement(request, task, type, props);
} else if (type === REACT_SUSPENSE_TYPE) {
@ -505,6 +764,7 @@ function spawnNewSuspendedTask(
task.blockedBoundary,
newSegment,
task.abortSet,
task.legacyContext,
task.assignID,
);
// We've delegated the assignment.
@ -521,7 +781,8 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
// Snapshot the current context in case something throws to interrupt the
// process.
const previousContext = task.blockedSegment.formatContext;
const previousFormatContext = task.blockedSegment.formatContext;
const previousLegacyContext = task.legacyContext;
try {
return renderNodeDestructive(request, task, node);
} catch (x) {
@ -529,7 +790,8 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
spawnNewSuspendedTask(request, task, x);
// Restore the context. We assume that this will be restored by the inner
// functions in case nothing throws so we don't use "finally" here.
task.blockedSegment.formatContext = previousContext;
task.blockedSegment.formatContext = previousFormatContext;
task.legacyContext = previousLegacyContext;
} else {
// We assume that we don't need the correct context.
// Let's terminate the rest of the tree and don't render any siblings.