Unify traversal logic in createElement

This moves all type traversal into createElement. When lazy resolves, we call createElement once to re-check.
This commit is contained in:
Dan Abramov
2018-11-28 18:22:31 +00:00
parent cf68b96413
commit 2e77ca47fe
3 changed files with 89 additions and 171 deletions
+11 -133
View File
@@ -13,6 +13,7 @@ import type {FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {SuspenseState} from './ReactFiberSuspenseComponent';
import React from 'react';
import checkPropTypes from 'prop-types/checkPropTypes';
import {
@@ -50,11 +51,11 @@ import {
debugRenderPhaseSideEffectsForStrictMode,
enableProfilerTimer,
} from 'shared/ReactFeatureFlags';
import isValidElementType from 'shared/isValidElementType';
import invariant from 'shared/invariant';
import shallowEqual from 'shared/shallowEqual';
import getComponentName from 'shared/getComponentName';
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
import warning from 'shared/warning';
import warningWithoutStack from 'shared/warningWithoutStack';
@@ -214,23 +215,6 @@ function updateForwardRef(
nextProps: any,
renderExpirationTime: ExpirationTime,
) {
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
const innerPropTypes = Component.propTypes;
if (innerPropTypes) {
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentName(Component),
getCurrentFiberStackInDev,
);
}
}
}
const render = Component.render;
const ref = workInProgress.ref;
@@ -292,20 +276,6 @@ function updateMemoComponent(
renderExpirationTime,
);
}
if (__DEV__) {
const innerPropTypes = type.propTypes;
if (innerPropTypes) {
// Inner memo component props aren't currently validated in createElement.
// We could move it there, but we'd still need this for lazy code path.
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentName(type),
getCurrentFiberStackInDev,
);
}
}
let child = createFiberFromTypeAndProps(
Component.type,
null,
@@ -319,21 +289,6 @@ function updateMemoComponent(
workInProgress.child = child;
return child;
}
if (__DEV__) {
const type = Component.type;
const innerPropTypes = type.propTypes;
if (innerPropTypes) {
// Inner memo component props aren't currently validated in createElement.
// We could move it there, but we'd still need this for lazy code path.
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentName(type),
getCurrentFiberStackInDev,
);
}
}
let currentChild = ((current.child: any): Fiber); // This is always exactly one child
if (updateExpirationTime < renderExpirationTime) {
// This will be the props with resolved defaultProps,
@@ -371,30 +326,6 @@ function updateSimpleMemoComponent(
updateExpirationTime,
renderExpirationTime: ExpirationTime,
): null | Fiber {
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
let outerMemoType = workInProgress.elementType;
if (outerMemoType.$$typeof === REACT_LAZY_TYPE) {
// We warn when you define propTypes on lazy()
// so let's just skip over it to find memo() outer wrapper.
// Inner props for memo are validated later.
outerMemoType = refineResolvedLazyComponent(outerMemoType);
}
const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
nextProps, // Resolved (SimpleMemoComponent has no defaultProps)
'prop',
getComponentName(outerMemoType),
getCurrentFiberStackInDev,
);
}
// Inner propTypes will be validated in the function component path.
}
}
if (current !== null && updateExpirationTime < renderExpirationTime) {
const prevProps = current.memoizedProps;
if (
@@ -484,23 +415,6 @@ function updateFunctionComponent(
nextProps: any,
renderExpirationTime,
) {
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
const innerPropTypes = Component.propTypes;
if (innerPropTypes) {
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentName(Component),
getCurrentFiberStackInDev,
);
}
}
}
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
@@ -535,23 +449,6 @@ function updateClassComponent(
nextProps,
renderExpirationTime: ExpirationTime,
) {
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
const innerPropTypes = Component.propTypes;
if (innerPropTypes) {
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentName(Component),
getCurrentFiberStackInDev,
);
}
}
}
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
@@ -901,6 +798,15 @@ function mountLazyComponent(
startWorkTimer(workInProgress);
const resolvedProps = resolveDefaultProps(Component, props);
let child;
if (__DEV__) {
// Trigger propTypes validation for the resolved type now that
// createElement() can look deeper and find propTypes and defaultProps.
if (isValidElementType(Component)) {
React.createElement(Component, resolvedProps);
}
}
switch (resolvedTag) {
case FunctionComponent: {
child = updateFunctionComponent(
@@ -933,20 +839,6 @@ function mountLazyComponent(
break;
}
case MemoComponent: {
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
const outerPropTypes = Component.propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop',
getComponentName(Component),
getCurrentFiberStackInDev,
);
}
}
}
child = updateMemoComponent(
null,
workInProgress,
@@ -1877,20 +1769,6 @@ function beginWork(
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
const outerPropTypes = type.propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop',
getComponentName(type),
getCurrentFiberStackInDev,
);
}
}
}
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
+77 -38
View File
@@ -15,10 +15,12 @@
import lowPriorityWarning from 'shared/lowPriorityWarning';
import isValidElementType from 'shared/isValidElementType';
import getComponentName from 'shared/getComponentName';
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
import {
getIteratorFn,
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
REACT_LAZY_TYPE,
REACT_FRAGMENT_TYPE,
REACT_ELEMENT_TYPE,
} from 'shared/ReactSymbols';
@@ -178,6 +180,61 @@ function validateChildKeys(node, parentType) {
}
}
function validatePropTypesRecursively(type, props) {
if (type === null || type === undefined || typeof type === 'string') {
return;
}
let propTypes;
let innerType;
if (typeof type === 'function') {
propTypes = type.propTypes;
} else if (typeof type === 'object') {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE: {
propTypes = type.propTypes;
break;
}
case REACT_LAZY_TYPE: {
propTypes = type.propTypes;
innerType = refineResolvedLazyComponent(type);
break;
}
case REACT_MEMO_TYPE: {
propTypes = type.propTypes;
innerType = type.type;
break;
}
}
}
// Common case.
if (propTypes) {
const name = getComponentName(type);
checkPropTypes(
propTypes,
props,
'prop',
name,
ReactDebugCurrentFrame.getStackAddendum,
);
}
// A type may have an inner type (e.g. React.memo or React.lazy).
// That's what the recursive case is for.
if (innerType) {
// Inner type may have its own defaultProps
let innerProps = props;
if (innerType && innerType.defaultProps) {
innerProps = {...props};
const innerDefaultProps = innerType.defaultProps;
for (const propName in innerDefaultProps) {
if (innerProps[propName] === undefined) {
innerProps[propName] = innerDefaultProps[propName];
}
}
}
validatePropTypesRecursively(innerType, innerProps);
}
}
/**
* Given an element, validate that its props follow the propTypes definition,
* provided by the type.
@@ -189,45 +246,27 @@ function validatePropTypes(element) {
if (type === null || type === undefined || typeof type === 'string') {
return;
}
const name = getComponentName(type);
let propTypes;
setCurrentlyValidatingElement(element);
validatePropTypesRecursively(type, element.props);
setCurrentlyValidatingElement(null);
if (typeof type === 'function') {
propTypes = type.propTypes;
} else if (
typeof type === 'object' &&
(type.$$typeof === REACT_FORWARD_REF_TYPE ||
// Note: Memo only checks outer props here.
// Inner props are checked in the reconciler.
type.$$typeof === REACT_MEMO_TYPE)
) {
propTypes = type.propTypes;
} else {
return;
}
if (propTypes) {
setCurrentlyValidatingElement(element);
checkPropTypes(
propTypes,
element.props,
'prop',
name,
ReactDebugCurrentFrame.getStackAddendum,
);
setCurrentlyValidatingElement(null);
} else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true;
warningWithoutStack(
false,
'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?',
name || 'Unknown',
);
}
if (typeof type.getDefaultProps === 'function') {
warningWithoutStack(
type.getDefaultProps.isReactClassApproved,
'getDefaultProps is only used on classic React.createClass ' +
'definitions. Use a static property named `defaultProps` instead.',
);
if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true;
warningWithoutStack(
false,
'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?',
getComponentName(type) || 'Unknown',
);
}
if (typeof type.getDefaultProps === 'function') {
warningWithoutStack(
type.getDefaultProps.isReactClassApproved,
'getDefaultProps is only used on classic React.createClass ' +
'definitions. Use a static property named `defaultProps` instead.',
);
}
}
}
+1
View File
@@ -13,6 +13,7 @@ const UMD_PROFILING = bundleTypes.UMD_PROFILING;
const HAS_NO_SIDE_EFFECTS_ON_IMPORT = false;
// const HAS_SIDE_EFFECTS_ON_IMPORT = true;
const importSideEffects = Object.freeze({
react: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
'prop-types/checkPropTypes': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
deepFreezeAndThrowOnMutationInDev: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
scheduler: HAS_NO_SIDE_EFFECTS_ON_IMPORT,