[Fizz] Implement Classes (#21200)
* Legacy context * Port Classes from Fiber to Fizz
This commit is contained in:
parent
75c616554d
commit
cf45a623a1
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue