You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
8.9 KiB
252 lines
8.9 KiB
/** |
|
* Copyright (c) 2014-present, Facebook, Inc. |
|
* |
|
* 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. |
|
*/ |
|
|
|
'use strict'; |
|
|
|
var ReactCurrentOwner = require('./ReactCurrentOwner'); |
|
var ReactComponentTreeHook = require('./ReactComponentTreeHook'); |
|
var ReactElement = require('./ReactElement'); |
|
|
|
var checkReactTypeSpec = require('./checkReactTypeSpec'); |
|
|
|
var canDefineProperty = require('./canDefineProperty'); |
|
var getIteratorFn = require('./getIteratorFn'); |
|
var warning = require('fbjs/lib/warning'); |
|
var lowPriorityWarning = require('./lowPriorityWarning'); |
|
|
|
function getDeclarationErrorAddendum() { |
|
if (ReactCurrentOwner.current) { |
|
var name = ReactCurrentOwner.current.getName(); |
|
if (name) { |
|
return ' Check the render method of `' + name + '`.'; |
|
} |
|
} |
|
return ''; |
|
} |
|
|
|
function getSourceInfoErrorAddendum(elementProps) { |
|
if (elementProps !== null && elementProps !== undefined && elementProps.__source !== undefined) { |
|
var source = elementProps.__source; |
|
var fileName = source.fileName.replace(/^.*[\\\/]/, ''); |
|
var lineNumber = source.lineNumber; |
|
return ' Check 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. |
|
*/ |
|
var ownerHasKeyUseWarning = {}; |
|
|
|
function getCurrentComponentErrorInfo(parentType) { |
|
var info = getDeclarationErrorAddendum(); |
|
|
|
if (!info) { |
|
var parentName = typeof parentType === 'string' ? parentType : parentType.displayName || parentType.name; |
|
if (parentName) { |
|
info = ' Check 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; |
|
|
|
var memoizer = ownerHasKeyUseWarning.uniqueKey || (ownerHasKeyUseWarning.uniqueKey = {}); |
|
|
|
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType); |
|
if (memoizer[currentComponentErrorInfo]) { |
|
return; |
|
} |
|
memoizer[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. |
|
var childOwner = ''; |
|
if (element && element._owner && element._owner !== ReactCurrentOwner.current) { |
|
// Give the component that originally created this child. |
|
childOwner = ' It was passed a child from ' + element._owner.getName() + '.'; |
|
} |
|
|
|
process.env.NODE_ENV !== 'production' ? warning(false, 'Each child in an array or iterator should have a unique "key" prop.' + '%s%s See https://fb.me/react-warning-keys for more information.%s', currentComponentErrorInfo, childOwner, ReactComponentTreeHook.getCurrentStackAddendum(element)) : void 0; |
|
} |
|
|
|
/** |
|
* 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 (var i = 0; i < node.length; i++) { |
|
var child = node[i]; |
|
if (ReactElement.isValidElement(child)) { |
|
validateExplicitKey(child, parentType); |
|
} |
|
} |
|
} else if (ReactElement.isValidElement(node)) { |
|
// This element was passed in a valid location. |
|
if (node._store) { |
|
node._store.validated = true; |
|
} |
|
} else if (node) { |
|
var iteratorFn = getIteratorFn(node); |
|
// Entry iterators provide implicit keys. |
|
if (iteratorFn) { |
|
if (iteratorFn !== node.entries) { |
|
var iterator = iteratorFn.call(node); |
|
var step; |
|
while (!(step = iterator.next()).done) { |
|
if (ReactElement.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) { |
|
var componentClass = element.type; |
|
if (typeof componentClass !== 'function') { |
|
return; |
|
} |
|
var name = componentClass.displayName || componentClass.name; |
|
if (componentClass.propTypes) { |
|
checkReactTypeSpec(componentClass.propTypes, element.props, 'prop', name, element, null); |
|
} |
|
if (typeof componentClass.getDefaultProps === 'function') { |
|
process.env.NODE_ENV !== 'production' ? warning(componentClass.getDefaultProps.isReactClassApproved, 'getDefaultProps is only used on classic React.createClass ' + 'definitions. Use a static property named `defaultProps` instead.') : void 0; |
|
} |
|
} |
|
|
|
var ReactElementValidator = { |
|
createElement: function (type, props, children) { |
|
var validType = typeof type === 'string' || typeof type === 'function'; |
|
// 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) { |
|
if (typeof type !== 'function' && typeof type !== 'string') { |
|
var 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."; |
|
} |
|
|
|
var sourceInfo = getSourceInfoErrorAddendum(props); |
|
if (sourceInfo) { |
|
info += sourceInfo; |
|
} else { |
|
info += getDeclarationErrorAddendum(); |
|
} |
|
|
|
info += ReactComponentTreeHook.getCurrentStackAddendum(); |
|
|
|
var currentSource = props !== null && props !== undefined && props.__source !== undefined ? props.__source : null; |
|
ReactComponentTreeHook.pushNonStandardWarningStack(true, currentSource); |
|
process.env.NODE_ENV !== 'production' ? 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', type == null ? type : typeof type, info) : void 0; |
|
ReactComponentTreeHook.popNonStandardWarningStack(); |
|
} |
|
} |
|
|
|
var element = ReactElement.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 (var i = 2; i < arguments.length; i++) { |
|
validateChildKeys(arguments[i], type); |
|
} |
|
} |
|
|
|
validatePropTypes(element); |
|
|
|
return element; |
|
}, |
|
|
|
createFactory: function (type) { |
|
var validatedFactory = ReactElementValidator.createElement.bind(null, type); |
|
// Legacy hook TODO: Warn if this is accessed |
|
validatedFactory.type = type; |
|
|
|
if (process.env.NODE_ENV !== 'production') { |
|
if (canDefineProperty) { |
|
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; |
|
}, |
|
|
|
cloneElement: function (element, props, children) { |
|
var newElement = ReactElement.cloneElement.apply(this, arguments); |
|
for (var i = 2; i < arguments.length; i++) { |
|
validateChildKeys(arguments[i], newElement.type); |
|
} |
|
validatePropTypes(newElement); |
|
return newElement; |
|
} |
|
}; |
|
|
|
module.exports = ReactElementValidator; |