UNPKG

react-esm

Version:

React is a JavaScript library for building user interfaces.

322 lines (262 loc) 10.6 kB
/** * 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. */ /** * 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 lowPriorityWarning from 'shared/lowPriorityWarning'; import isValidElementType from 'shared/isValidElementType'; import getComponentName from 'shared/getComponentName'; import { getIteratorFn, REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE, REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE } from 'shared/ReactSymbols'; import checkPropTypes from 'prop-types/checkPropTypes'; import warning from 'shared/warning'; import warningWithoutStack from 'shared/warningWithoutStack'; import ReactCurrentOwner from "./ReactCurrentOwner.js"; import { isValidElement, createElement, cloneElement } from "./ReactElement.js"; import ReactDebugCurrentFrame, { setCurrentlyValidatingElement } from "./ReactDebugCurrentFrame.js"; let propTypesMisspellWarningShown; if (__DEV__) { propTypesMisspellWarningShown = false; } function getDeclarationErrorAddendum() { if (ReactCurrentOwner.current) { const name = getComponentName(ReactCurrentOwner.current.type); if (name) { return '\n\nCheck the render method of `' + name + '`.'; } } return ''; } function getSourceInfoErrorAddendum(elementProps) { if (elementProps !== null && elementProps !== undefined && elementProps.__source !== undefined) { const source = elementProps.__source; 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) { let info = getDeclarationErrorAddendum(); if (!info) { const parentName = typeof parentType === 'string' ? parentType : parentType.displayName || parentType.name; 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 (!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 ${getComponentName(element._owner.type)}.`; } setCurrentlyValidatingElement(element); if (__DEV__) { warning(false, 'Each child in a list should have a unique "key" prop.' + '%s%s See https://fb.me/react-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 (typeof node !== 'object') { return; } if (Array.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 if (node) { 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) { const type = element.type; if (type === null || type === undefined || typeof type === 'string') { return; } const name = getComponentName(type); 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) { 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.'); } } /** * Given a fragment, validate that it can only be provided with fragment props * @param {ReactElement} fragment */ function validateFragmentProps(fragment) { setCurrentlyValidatingElement(fragment); const keys = Object.keys(fragment.props); for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (key !== 'children' && key !== 'key') { warning(false, 'Invalid prop `%s` supplied to `React.Fragment`. ' + 'React.Fragment can only have `key` and `children` props.', key); break; } } if (fragment.ref !== null) { warning(false, 'Invalid attribute `ref` supplied to `React.Fragment`.'); } setCurrentlyValidatingElement(null); } export function createElementWithValidation(type, props, children) { 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(props); if (sourceInfo) { info += sourceInfo; } else { info += getDeclarationErrorAddendum(); } let typeString; if (type === null) { typeString = 'null'; } else if (Array.isArray(type)) { typeString = 'array'; } else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) { typeString = `<${getComponentName(type.type) || 'Unknown'} />`; info = ' Did you accidentally export a JSX literal instead of a component?'; } else { typeString = typeof type; } warning(false, 'React.createElement: 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 = createElement.apply(this, arguments); // 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) { for (let i = 2; i < arguments.length; i++) { validateChildKeys(arguments[i], type); } } if (type === REACT_FRAGMENT_TYPE) { validateFragmentProps(element); } else { validatePropTypes(element); } return element; } export function createFactoryWithValidation(type) { const validatedFactory = createElementWithValidation.bind(null, type); validatedFactory.type = type; // Legacy hook: remove it if (__DEV__) { Object.defineProperty(validatedFactory, 'type', { enumerable: false, get: function () { lowPriorityWarning(false, 'Factory.type is deprecated. Access the class directly ' + 'before passing it to createFactory.'); Object.defineProperty(this, 'type', { value: type }); return type; } }); } return validatedFactory; } export function cloneElementWithValidation(element, props, children) { const newElement = cloneElement.apply(this, arguments); for (let i = 2; i < arguments.length; i++) { validateChildKeys(arguments[i], newElement.type); } validatePropTypes(newElement); return newElement; }