mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Combine ReactJSXElementValidator with main module (#28317)
There are too many layers to the JSX runtime implementation. I think basically everything should be implemented in a single file, so that's what I'm going to do. As a first step, this deletes ReactJSXElementValidator and moves all the code into ReactJSXElement. I can already see how this will help us remove more indirections in the future. Next I'm going to do start moving the `createElement` runtime into this module as well, since there's a lot of duplicated code.
This commit is contained in:
@@ -8,15 +8,21 @@
|
||||
*/
|
||||
import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
|
||||
import {
|
||||
jsxWithValidationStatic,
|
||||
jsxWithValidationDynamic,
|
||||
jsxWithValidation,
|
||||
} from './ReactJSXElementValidator';
|
||||
import {jsx as jsxProd} from './ReactJSXElement';
|
||||
const jsx: any = __DEV__ ? jsxWithValidationDynamic : jsxProd;
|
||||
jsxProd,
|
||||
jsxProdSignatureRunningInDevWithDynamicChildren,
|
||||
jsxProdSignatureRunningInDevWithStaticChildren,
|
||||
jsxDEV as _jsxDEV,
|
||||
} from './ReactJSXElement';
|
||||
|
||||
const jsx: any = __DEV__
|
||||
? jsxProdSignatureRunningInDevWithDynamicChildren
|
||||
: jsxProd;
|
||||
// we may want to special case jsxs internally to take advantage of static children.
|
||||
// for now we can ship identical prod functions
|
||||
const jsxs: any = __DEV__ ? jsxWithValidationStatic : jsxProd;
|
||||
const jsxDEV: any = __DEV__ ? jsxWithValidation : undefined;
|
||||
const jsxs: any = __DEV__
|
||||
? jsxProdSignatureRunningInDevWithStaticChildren
|
||||
: jsxProd;
|
||||
|
||||
const jsxDEV: any = __DEV__ ? _jsxDEV : undefined;
|
||||
|
||||
export {REACT_FRAGMENT_TYPE as Fragment, jsx, jsxs, jsxDEV};
|
||||
|
||||
@@ -8,10 +8,23 @@
|
||||
import getComponentNameFromType from 'shared/getComponentNameFromType';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import hasOwnProperty from 'shared/hasOwnProperty';
|
||||
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
|
||||
import {
|
||||
getIteratorFn,
|
||||
REACT_ELEMENT_TYPE,
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
REACT_MEMO_TYPE,
|
||||
REACT_FRAGMENT_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {checkKeyStringCoercion} from 'shared/CheckStringCoercion';
|
||||
import isValidElementType from 'shared/isValidElementType';
|
||||
import isArray from 'shared/isArray';
|
||||
import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
|
||||
import checkPropTypes from 'shared/checkPropTypes';
|
||||
|
||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
||||
|
||||
const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference');
|
||||
|
||||
let specialPropKeyWarningShown;
|
||||
let specialPropRefWarningShown;
|
||||
@@ -192,7 +205,7 @@ function ReactElement(type, key, ref, self, source, owner, props) {
|
||||
* @param {object} props
|
||||
* @param {string} key
|
||||
*/
|
||||
export function jsx(type, config, maybeKey) {
|
||||
export function jsxProd(type, config, maybeKey) {
|
||||
let propName;
|
||||
|
||||
// Reserved names are extracted
|
||||
@@ -259,14 +272,157 @@ export function jsx(type, config, maybeKey) {
|
||||
);
|
||||
}
|
||||
|
||||
// While `jsxDEV` should never be called when running in production, we do
|
||||
// support `jsx` and `jsxs` when running in development. This supports the case
|
||||
// where a third-party dependency ships code that was compiled for production;
|
||||
// we want to still provide warnings in development.
|
||||
//
|
||||
// So these functions are the _dev_ implementations of the _production_
|
||||
// API signatures.
|
||||
//
|
||||
// Since these functions are dev-only, it's ok to add an indirection here. They
|
||||
// only exist to provide different versions of `isStaticChildren`. (We shouldn't
|
||||
// use this pattern for the prod versions, though, because it will add an call
|
||||
// frame.)
|
||||
export function jsxProdSignatureRunningInDevWithDynamicChildren(
|
||||
type,
|
||||
config,
|
||||
maybeKey,
|
||||
source,
|
||||
self,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
const isStaticChildren = false;
|
||||
return jsxDEV(type, config, maybeKey, isStaticChildren, source, self);
|
||||
}
|
||||
}
|
||||
|
||||
export function jsxProdSignatureRunningInDevWithStaticChildren(
|
||||
type,
|
||||
config,
|
||||
maybeKey,
|
||||
source,
|
||||
self,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
const isStaticChildren = true;
|
||||
return jsxDEV(type, config, maybeKey, isStaticChildren, source, self);
|
||||
}
|
||||
}
|
||||
|
||||
const didWarnAboutKeySpread = {};
|
||||
|
||||
/**
|
||||
* https://github.com/reactjs/rfcs/pull/107
|
||||
* @param {*} type
|
||||
* @param {object} props
|
||||
* @param {string} key
|
||||
*/
|
||||
export function jsxDEV(type, config, maybeKey, source, self) {
|
||||
export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) {
|
||||
if (__DEV__) {
|
||||
if (!isValidElementType(type)) {
|
||||
// This is an invalid element type.
|
||||
//
|
||||
// We warn in this case but don't throw. We expect the element creation to
|
||||
// succeed and there will likely be errors in render.
|
||||
let info = '';
|
||||
if (
|
||||
type === undefined ||
|
||||
(typeof type === 'object' &&
|
||||
type !== null &&
|
||||
Object.keys(type).length === 0)
|
||||
) {
|
||||
info +=
|
||||
' You likely forgot to export your component from the file ' +
|
||||
"it's defined in, or you might have mixed up default and named imports.";
|
||||
}
|
||||
|
||||
const sourceInfo = getSourceInfoErrorAddendum(source);
|
||||
if (sourceInfo) {
|
||||
info += sourceInfo;
|
||||
} else {
|
||||
info += getDeclarationErrorAddendum();
|
||||
}
|
||||
|
||||
let typeString;
|
||||
if (type === null) {
|
||||
typeString = 'null';
|
||||
} else if (isArray(type)) {
|
||||
typeString = 'array';
|
||||
} else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
|
||||
typeString = `<${getComponentNameFromType(type.type) || 'Unknown'} />`;
|
||||
info =
|
||||
' Did you accidentally export a JSX literal instead of a component?';
|
||||
} else {
|
||||
typeString = typeof type;
|
||||
}
|
||||
|
||||
console.error(
|
||||
'React.jsx: type is invalid -- expected a string (for ' +
|
||||
'built-in components) or a class/function (for composite ' +
|
||||
'components) but got: %s.%s',
|
||||
typeString,
|
||||
info,
|
||||
);
|
||||
} else {
|
||||
// This is a valid element type.
|
||||
|
||||
// Skip key warning if the type isn't valid since our key validation logic
|
||||
// doesn't expect a non-string/function type and can throw confusing
|
||||
// errors. We don't want exception behavior to differ between dev and
|
||||
// prod. (Rendering will throw with a helpful message and as soon as the
|
||||
// type is fixed, the key warnings will appear.)
|
||||
const children = config.children;
|
||||
if (children !== undefined) {
|
||||
if (isStaticChildren) {
|
||||
if (isArray(children)) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
validateChildKeys(children[i], type);
|
||||
}
|
||||
|
||||
if (Object.freeze) {
|
||||
Object.freeze(children);
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
'React.jsx: Static children should always be an array. ' +
|
||||
'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
|
||||
'Use the Babel transform instead.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
validateChildKeys(children, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Warn about key spread regardless of whether the type is valid.
|
||||
if (hasOwnProperty.call(config, 'key')) {
|
||||
const componentName = getComponentNameFromType(type);
|
||||
const keys = Object.keys(config).filter(k => k !== 'key');
|
||||
const beforeExample =
|
||||
keys.length > 0
|
||||
? '{key: someKey, ' + keys.join(': ..., ') + ': ...}'
|
||||
: '{key: someKey}';
|
||||
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
|
||||
const afterExample =
|
||||
keys.length > 0 ? '{' + keys.join(': ..., ') + ': ...}' : '{}';
|
||||
console.error(
|
||||
'A props object containing a "key" prop is being spread into JSX:\n' +
|
||||
' let props = %s;\n' +
|
||||
' <%s {...props} />\n' +
|
||||
'React keys must be passed directly to JSX without using spread:\n' +
|
||||
' let props = %s;\n' +
|
||||
' <%s key={someKey} {...props} />',
|
||||
beforeExample,
|
||||
componentName,
|
||||
afterExample,
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutKeySpread[componentName + beforeExample] = true;
|
||||
}
|
||||
}
|
||||
|
||||
let propName;
|
||||
|
||||
// Reserved names are extracted
|
||||
@@ -336,7 +492,7 @@ export function jsxDEV(type, config, maybeKey, source, self) {
|
||||
}
|
||||
}
|
||||
|
||||
return ReactElement(
|
||||
const element = ReactElement(
|
||||
type,
|
||||
key,
|
||||
ref,
|
||||
@@ -345,5 +501,266 @@ export function jsxDEV(type, config, maybeKey, source, self) {
|
||||
ReactCurrentOwner.current,
|
||||
props,
|
||||
);
|
||||
|
||||
if (type === REACT_FRAGMENT_TYPE) {
|
||||
validateFragmentProps(element);
|
||||
} else {
|
||||
validatePropTypes(element);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
function getDeclarationErrorAddendum() {
|
||||
if (__DEV__) {
|
||||
if (ReactCurrentOwner.current) {
|
||||
const name = getComponentNameFromType(ReactCurrentOwner.current.type);
|
||||
if (name) {
|
||||
return '\n\nCheck the render method of `' + name + '`.';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceInfoErrorAddendum(source) {
|
||||
if (__DEV__) {
|
||||
if (source !== undefined) {
|
||||
const fileName = source.fileName.replace(/^.*[\\\/]/, '');
|
||||
const lineNumber = source.lineNumber;
|
||||
return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that every element either is passed in a static location, in an
|
||||
* array with an explicit keys property defined, or in an object literal
|
||||
* with valid key property.
|
||||
*
|
||||
* @internal
|
||||
* @param {ReactNode} node Statically passed child of any type.
|
||||
* @param {*} parentType node's parent's type.
|
||||
*/
|
||||
function validateChildKeys(node, parentType) {
|
||||
if (__DEV__) {
|
||||
if (typeof node !== 'object' || !node) {
|
||||
return;
|
||||
}
|
||||
if (node.$$typeof === REACT_CLIENT_REFERENCE) {
|
||||
// This is a reference to a client component so it's unknown.
|
||||
} else if (isArray(node)) {
|
||||
for (let i = 0; i < node.length; i++) {
|
||||
const child = node[i];
|
||||
if (isValidElement(child)) {
|
||||
validateExplicitKey(child, parentType);
|
||||
}
|
||||
}
|
||||
} else if (isValidElement(node)) {
|
||||
// This element was passed in a valid location.
|
||||
if (node._store) {
|
||||
node._store.validated = true;
|
||||
}
|
||||
} else {
|
||||
const iteratorFn = getIteratorFn(node);
|
||||
if (typeof iteratorFn === 'function') {
|
||||
// Entry iterators used to provide implicit keys,
|
||||
// but now we print a separate warning for them later.
|
||||
if (iteratorFn !== node.entries) {
|
||||
const iterator = iteratorFn.call(node);
|
||||
let step;
|
||||
while (!(step = iterator.next()).done) {
|
||||
if (isValidElement(step.value)) {
|
||||
validateExplicitKey(step.value, parentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the object is a ReactElement.
|
||||
* See https://reactjs.org/docs/react-api.html#isvalidelement
|
||||
* @param {?object} object
|
||||
* @return {boolean} True if `object` is a ReactElement.
|
||||
* @final
|
||||
*/
|
||||
export function isValidElement(object) {
|
||||
if (__DEV__) {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.$$typeof === REACT_ELEMENT_TYPE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ownerHasKeyUseWarning = {};
|
||||
|
||||
/**
|
||||
* Warn if the element doesn't have an explicit key assigned to it.
|
||||
* This element is in an array. The array could grow and shrink or be
|
||||
* reordered. All children that haven't already been validated are required to
|
||||
* have a "key" property assigned to it. Error statuses are cached so a warning
|
||||
* will only be shown once.
|
||||
*
|
||||
* @internal
|
||||
* @param {ReactElement} element Element that requires a key.
|
||||
* @param {*} parentType element's parent's type.
|
||||
*/
|
||||
function validateExplicitKey(element, parentType) {
|
||||
if (__DEV__) {
|
||||
if (!element._store || element._store.validated || element.key != null) {
|
||||
return;
|
||||
}
|
||||
element._store.validated = true;
|
||||
|
||||
const currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
|
||||
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
|
||||
return;
|
||||
}
|
||||
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
|
||||
|
||||
// Usually the current owner is the offender, but if it accepts children as a
|
||||
// property, it may be the creator of the child that's responsible for
|
||||
// assigning it a key.
|
||||
let childOwner = '';
|
||||
if (
|
||||
element &&
|
||||
element._owner &&
|
||||
element._owner !== ReactCurrentOwner.current
|
||||
) {
|
||||
// Give the component that originally created this child.
|
||||
childOwner = ` It was passed a child from ${getComponentNameFromType(
|
||||
element._owner.type,
|
||||
)}.`;
|
||||
}
|
||||
|
||||
setCurrentlyValidatingElement(element);
|
||||
console.error(
|
||||
'Each child in a list should have a unique "key" prop.' +
|
||||
'%s%s See https://reactjs.org/link/warning-keys for more information.',
|
||||
currentComponentErrorInfo,
|
||||
childOwner,
|
||||
);
|
||||
setCurrentlyValidatingElement(null);
|
||||
}
|
||||
}
|
||||
|
||||
function setCurrentlyValidatingElement(element) {
|
||||
if (__DEV__) {
|
||||
if (element) {
|
||||
const owner = element._owner;
|
||||
const stack = describeUnknownElementTypeFrameInDEV(
|
||||
element.type,
|
||||
owner ? owner.type : null,
|
||||
);
|
||||
ReactDebugCurrentFrame.setExtraStackFrame(stack);
|
||||
} else {
|
||||
ReactDebugCurrentFrame.setExtraStackFrame(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentComponentErrorInfo(parentType) {
|
||||
if (__DEV__) {
|
||||
let info = getDeclarationErrorAddendum();
|
||||
|
||||
if (!info) {
|
||||
const parentName = getComponentNameFromType(parentType);
|
||||
if (parentName) {
|
||||
info = `\n\nCheck the top-level render call using <${parentName}>.`;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a fragment, validate that it can only be provided with fragment props
|
||||
* @param {ReactElement} fragment
|
||||
*/
|
||||
function validateFragmentProps(fragment) {
|
||||
if (__DEV__) {
|
||||
const keys = Object.keys(fragment.props);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (key !== 'children' && key !== 'key') {
|
||||
setCurrentlyValidatingElement(fragment);
|
||||
console.error(
|
||||
'Invalid prop `%s` supplied to `React.Fragment`. ' +
|
||||
'React.Fragment can only have `key` and `children` props.',
|
||||
key,
|
||||
);
|
||||
setCurrentlyValidatingElement(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fragment.ref !== null) {
|
||||
setCurrentlyValidatingElement(fragment);
|
||||
console.error('Invalid attribute `ref` supplied to `React.Fragment`.');
|
||||
setCurrentlyValidatingElement(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let propTypesMisspellWarningShown = false;
|
||||
|
||||
/**
|
||||
* Given an element, validate that its props follow the propTypes definition,
|
||||
* provided by the type.
|
||||
*
|
||||
* @param {ReactElement} element
|
||||
*/
|
||||
function validatePropTypes(element) {
|
||||
if (__DEV__) {
|
||||
const type = element.type;
|
||||
if (type === null || type === undefined || typeof type === 'string') {
|
||||
return;
|
||||
}
|
||||
if (type.$$typeof === REACT_CLIENT_REFERENCE) {
|
||||
return;
|
||||
}
|
||||
let propTypes;
|
||||
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) {
|
||||
// Intentionally inside to avoid triggering lazy initializers:
|
||||
const name = getComponentNameFromType(type);
|
||||
checkPropTypes(propTypes, element.props, 'prop', name, element);
|
||||
} else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {
|
||||
propTypesMisspellWarningShown = true;
|
||||
// Intentionally inside to avoid triggering lazy initializers:
|
||||
const name = getComponentNameFromType(type);
|
||||
console.error(
|
||||
'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?',
|
||||
name || 'Unknown',
|
||||
);
|
||||
}
|
||||
if (
|
||||
typeof type.getDefaultProps === 'function' &&
|
||||
!type.getDefaultProps.isReactClassApproved
|
||||
) {
|
||||
console.error(
|
||||
'getDefaultProps is only used on classic React.createClass ' +
|
||||
'definitions. Use a static property named `defaultProps` instead.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,445 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ReactElementValidator provides a wrapper around a element factory
|
||||
* which validates the props passed to the element. This is intended to be
|
||||
* used only in DEV and could be replaced by a static type checker for languages
|
||||
* that support it.
|
||||
*/
|
||||
import isValidElementType from 'shared/isValidElementType';
|
||||
import getComponentNameFromType from 'shared/getComponentNameFromType';
|
||||
import checkPropTypes from 'shared/checkPropTypes';
|
||||
import {
|
||||
getIteratorFn,
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
REACT_MEMO_TYPE,
|
||||
REACT_FRAGMENT_TYPE,
|
||||
REACT_ELEMENT_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import hasOwnProperty from 'shared/hasOwnProperty';
|
||||
import isArray from 'shared/isArray';
|
||||
import {jsxDEV} from './ReactJSXElement';
|
||||
|
||||
import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
|
||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
||||
|
||||
const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference');
|
||||
|
||||
function setCurrentlyValidatingElement(element) {
|
||||
if (__DEV__) {
|
||||
if (element) {
|
||||
const owner = element._owner;
|
||||
const stack = describeUnknownElementTypeFrameInDEV(
|
||||
element.type,
|
||||
owner ? owner.type : null,
|
||||
);
|
||||
ReactDebugCurrentFrame.setExtraStackFrame(stack);
|
||||
} else {
|
||||
ReactDebugCurrentFrame.setExtraStackFrame(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let propTypesMisspellWarningShown;
|
||||
|
||||
if (__DEV__) {
|
||||
propTypesMisspellWarningShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the object is a ReactElement.
|
||||
* See https://reactjs.org/docs/react-api.html#isvalidelement
|
||||
* @param {?object} object
|
||||
* @return {boolean} True if `object` is a ReactElement.
|
||||
* @final
|
||||
*/
|
||||
export function isValidElement(object) {
|
||||
if (__DEV__) {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.$$typeof === REACT_ELEMENT_TYPE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getDeclarationErrorAddendum() {
|
||||
if (__DEV__) {
|
||||
if (ReactCurrentOwner.current) {
|
||||
const name = getComponentNameFromType(ReactCurrentOwner.current.type);
|
||||
if (name) {
|
||||
return '\n\nCheck the render method of `' + name + '`.';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceInfoErrorAddendum(source) {
|
||||
if (__DEV__) {
|
||||
if (source !== undefined) {
|
||||
const fileName = source.fileName.replace(/^.*[\\\/]/, '');
|
||||
const lineNumber = source.lineNumber;
|
||||
return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn if there's no key explicitly set on dynamic arrays of children or
|
||||
* object keys are not valid. This allows us to keep track of children between
|
||||
* updates.
|
||||
*/
|
||||
const ownerHasKeyUseWarning = {};
|
||||
|
||||
function getCurrentComponentErrorInfo(parentType) {
|
||||
if (__DEV__) {
|
||||
let info = getDeclarationErrorAddendum();
|
||||
|
||||
if (!info) {
|
||||
const parentName = getComponentNameFromType(parentType);
|
||||
if (parentName) {
|
||||
info = `\n\nCheck the top-level render call using <${parentName}>.`;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn if the element doesn't have an explicit key assigned to it.
|
||||
* This element is in an array. The array could grow and shrink or be
|
||||
* reordered. All children that haven't already been validated are required to
|
||||
* have a "key" property assigned to it. Error statuses are cached so a warning
|
||||
* will only be shown once.
|
||||
*
|
||||
* @internal
|
||||
* @param {ReactElement} element Element that requires a key.
|
||||
* @param {*} parentType element's parent's type.
|
||||
*/
|
||||
function validateExplicitKey(element, parentType) {
|
||||
if (__DEV__) {
|
||||
if (!element._store || element._store.validated || element.key != null) {
|
||||
return;
|
||||
}
|
||||
element._store.validated = true;
|
||||
|
||||
const currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
|
||||
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
|
||||
return;
|
||||
}
|
||||
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
|
||||
|
||||
// Usually the current owner is the offender, but if it accepts children as a
|
||||
// property, it may be the creator of the child that's responsible for
|
||||
// assigning it a key.
|
||||
let childOwner = '';
|
||||
if (
|
||||
element &&
|
||||
element._owner &&
|
||||
element._owner !== ReactCurrentOwner.current
|
||||
) {
|
||||
// Give the component that originally created this child.
|
||||
childOwner = ` It was passed a child from ${getComponentNameFromType(
|
||||
element._owner.type,
|
||||
)}.`;
|
||||
}
|
||||
|
||||
setCurrentlyValidatingElement(element);
|
||||
console.error(
|
||||
'Each child in a list should have a unique "key" prop.' +
|
||||
'%s%s See https://reactjs.org/link/warning-keys for more information.',
|
||||
currentComponentErrorInfo,
|
||||
childOwner,
|
||||
);
|
||||
setCurrentlyValidatingElement(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that every element either is passed in a static location, in an
|
||||
* array with an explicit keys property defined, or in an object literal
|
||||
* with valid key property.
|
||||
*
|
||||
* @internal
|
||||
* @param {ReactNode} node Statically passed child of any type.
|
||||
* @param {*} parentType node's parent's type.
|
||||
*/
|
||||
function validateChildKeys(node, parentType) {
|
||||
if (__DEV__) {
|
||||
if (typeof node !== 'object' || !node) {
|
||||
return;
|
||||
}
|
||||
if (node.$$typeof === REACT_CLIENT_REFERENCE) {
|
||||
// This is a reference to a client component so it's unknown.
|
||||
} else if (isArray(node)) {
|
||||
for (let i = 0; i < node.length; i++) {
|
||||
const child = node[i];
|
||||
if (isValidElement(child)) {
|
||||
validateExplicitKey(child, parentType);
|
||||
}
|
||||
}
|
||||
} else if (isValidElement(node)) {
|
||||
// This element was passed in a valid location.
|
||||
if (node._store) {
|
||||
node._store.validated = true;
|
||||
}
|
||||
} else {
|
||||
const iteratorFn = getIteratorFn(node);
|
||||
if (typeof iteratorFn === 'function') {
|
||||
// Entry iterators used to provide implicit keys,
|
||||
// but now we print a separate warning for them later.
|
||||
if (iteratorFn !== node.entries) {
|
||||
const iterator = iteratorFn.call(node);
|
||||
let step;
|
||||
while (!(step = iterator.next()).done) {
|
||||
if (isValidElement(step.value)) {
|
||||
validateExplicitKey(step.value, parentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an element, validate that its props follow the propTypes definition,
|
||||
* provided by the type.
|
||||
*
|
||||
* @param {ReactElement} element
|
||||
*/
|
||||
function validatePropTypes(element) {
|
||||
if (__DEV__) {
|
||||
const type = element.type;
|
||||
if (type === null || type === undefined || typeof type === 'string') {
|
||||
return;
|
||||
}
|
||||
if (type.$$typeof === REACT_CLIENT_REFERENCE) {
|
||||
return;
|
||||
}
|
||||
let propTypes;
|
||||
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) {
|
||||
// Intentionally inside to avoid triggering lazy initializers:
|
||||
const name = getComponentNameFromType(type);
|
||||
checkPropTypes(propTypes, element.props, 'prop', name, element);
|
||||
} else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {
|
||||
propTypesMisspellWarningShown = true;
|
||||
// Intentionally inside to avoid triggering lazy initializers:
|
||||
const name = getComponentNameFromType(type);
|
||||
console.error(
|
||||
'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?',
|
||||
name || 'Unknown',
|
||||
);
|
||||
}
|
||||
if (
|
||||
typeof type.getDefaultProps === 'function' &&
|
||||
!type.getDefaultProps.isReactClassApproved
|
||||
) {
|
||||
console.error(
|
||||
'getDefaultProps is only used on classic React.createClass ' +
|
||||
'definitions. Use a static property named `defaultProps` instead.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a fragment, validate that it can only be provided with fragment props
|
||||
* @param {ReactElement} fragment
|
||||
*/
|
||||
function validateFragmentProps(fragment) {
|
||||
if (__DEV__) {
|
||||
const keys = Object.keys(fragment.props);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (key !== 'children' && key !== 'key') {
|
||||
setCurrentlyValidatingElement(fragment);
|
||||
console.error(
|
||||
'Invalid prop `%s` supplied to `React.Fragment`. ' +
|
||||
'React.Fragment can only have `key` and `children` props.',
|
||||
key,
|
||||
);
|
||||
setCurrentlyValidatingElement(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fragment.ref !== null) {
|
||||
setCurrentlyValidatingElement(fragment);
|
||||
console.error('Invalid attribute `ref` supplied to `React.Fragment`.');
|
||||
setCurrentlyValidatingElement(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const didWarnAboutKeySpread = {};
|
||||
|
||||
export function jsxWithValidation(
|
||||
type,
|
||||
props,
|
||||
key,
|
||||
isStaticChildren,
|
||||
source,
|
||||
self,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
const validType = isValidElementType(type);
|
||||
|
||||
// We warn in this case but don't throw. We expect the element creation to
|
||||
// succeed and there will likely be errors in render.
|
||||
if (!validType) {
|
||||
let info = '';
|
||||
if (
|
||||
type === undefined ||
|
||||
(typeof type === 'object' &&
|
||||
type !== null &&
|
||||
Object.keys(type).length === 0)
|
||||
) {
|
||||
info +=
|
||||
' You likely forgot to export your component from the file ' +
|
||||
"it's defined in, or you might have mixed up default and named imports.";
|
||||
}
|
||||
|
||||
const sourceInfo = getSourceInfoErrorAddendum(source);
|
||||
if (sourceInfo) {
|
||||
info += sourceInfo;
|
||||
} else {
|
||||
info += getDeclarationErrorAddendum();
|
||||
}
|
||||
|
||||
let typeString;
|
||||
if (type === null) {
|
||||
typeString = 'null';
|
||||
} else if (isArray(type)) {
|
||||
typeString = 'array';
|
||||
} else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
|
||||
typeString = `<${getComponentNameFromType(type.type) || 'Unknown'} />`;
|
||||
info =
|
||||
' Did you accidentally export a JSX literal instead of a component?';
|
||||
} else {
|
||||
typeString = typeof type;
|
||||
}
|
||||
|
||||
console.error(
|
||||
'React.jsx: type is invalid -- expected a string (for ' +
|
||||
'built-in components) or a class/function (for composite ' +
|
||||
'components) but got: %s.%s',
|
||||
typeString,
|
||||
info,
|
||||
);
|
||||
}
|
||||
|
||||
const element = jsxDEV(type, props, key, source, self);
|
||||
|
||||
// The result can be nullish if a mock or a custom function is used.
|
||||
// TODO: Drop this when these are no longer allowed as the type argument.
|
||||
if (element == null) {
|
||||
return element;
|
||||
}
|
||||
|
||||
// Skip key warning if the type isn't valid since our key validation logic
|
||||
// doesn't expect a non-string/function type and can throw confusing errors.
|
||||
// We don't want exception behavior to differ between dev and prod.
|
||||
// (Rendering will throw with a helpful message and as soon as the type is
|
||||
// fixed, the key warnings will appear.)
|
||||
|
||||
if (validType) {
|
||||
const children = props.children;
|
||||
if (children !== undefined) {
|
||||
if (isStaticChildren) {
|
||||
if (isArray(children)) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
validateChildKeys(children[i], type);
|
||||
}
|
||||
|
||||
if (Object.freeze) {
|
||||
Object.freeze(children);
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
'React.jsx: Static children should always be an array. ' +
|
||||
'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
|
||||
'Use the Babel transform instead.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
validateChildKeys(children, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasOwnProperty.call(props, 'key')) {
|
||||
const componentName = getComponentNameFromType(type);
|
||||
const keys = Object.keys(props).filter(k => k !== 'key');
|
||||
const beforeExample =
|
||||
keys.length > 0
|
||||
? '{key: someKey, ' + keys.join(': ..., ') + ': ...}'
|
||||
: '{key: someKey}';
|
||||
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
|
||||
const afterExample =
|
||||
keys.length > 0 ? '{' + keys.join(': ..., ') + ': ...}' : '{}';
|
||||
console.error(
|
||||
'A props object containing a "key" prop is being spread into JSX:\n' +
|
||||
' let props = %s;\n' +
|
||||
' <%s {...props} />\n' +
|
||||
'React keys must be passed directly to JSX without using spread:\n' +
|
||||
' let props = %s;\n' +
|
||||
' <%s key={someKey} {...props} />',
|
||||
beforeExample,
|
||||
componentName,
|
||||
afterExample,
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutKeySpread[componentName + beforeExample] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === REACT_FRAGMENT_TYPE) {
|
||||
validateFragmentProps(element);
|
||||
} else {
|
||||
validatePropTypes(element);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
// These two functions exist to still get child warnings in dev
|
||||
// even with the prod transform. This means that jsxDEV is purely
|
||||
// opt-in behavior for better messages but that we won't stop
|
||||
// giving you warnings if you use production apis.
|
||||
export function jsxWithValidationStatic(type, props, key) {
|
||||
if (__DEV__) {
|
||||
return jsxWithValidation(type, props, key, true);
|
||||
}
|
||||
}
|
||||
|
||||
export function jsxWithValidationDynamic(type, props, key) {
|
||||
if (__DEV__) {
|
||||
return jsxWithValidation(type, props, key, false);
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,23 @@
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// These are implementations of the jsx APIs for React Server runtimes.
|
||||
import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
|
||||
import {
|
||||
jsxWithValidationStatic,
|
||||
jsxWithValidationDynamic,
|
||||
} from './ReactJSXElementValidator';
|
||||
import {jsx as jsxProd} from './ReactJSXElement';
|
||||
const jsx: any = __DEV__ ? jsxWithValidationDynamic : jsxProd;
|
||||
jsxProd,
|
||||
jsxProdSignatureRunningInDevWithDynamicChildren,
|
||||
jsxProdSignatureRunningInDevWithStaticChildren,
|
||||
jsxDEV as _jsxDEV,
|
||||
} from './ReactJSXElement';
|
||||
|
||||
const jsx: any = __DEV__
|
||||
? jsxProdSignatureRunningInDevWithDynamicChildren
|
||||
: jsxProd;
|
||||
// we may want to special case jsxs internally to take advantage of static children.
|
||||
// for now we can ship identical prod functions
|
||||
const jsxs: any = __DEV__ ? jsxWithValidationStatic : jsxProd;
|
||||
const jsxs: any = __DEV__
|
||||
? jsxProdSignatureRunningInDevWithStaticChildren
|
||||
: jsxProd;
|
||||
|
||||
export {REACT_FRAGMENT_TYPE as Fragment, jsx, jsxs};
|
||||
const jsxDEV: any = __DEV__ ? _jsxDEV : undefined;
|
||||
|
||||
export {REACT_FRAGMENT_TYPE as Fragment, jsx, jsxs, jsxDEV};
|
||||
|
||||
Reference in New Issue
Block a user