mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Revert "Move defaultProps resolution and type validation to the descriptor"
This reverts commit ff52e3df00.
Too many things broke with transferPropsTo
This commit is contained in:
@@ -20,7 +20,6 @@
|
||||
"use strict";
|
||||
|
||||
var ReactDescriptor = require('ReactDescriptor');
|
||||
var ReactDescriptorValidator = require('ReactDescriptorValidator');
|
||||
var ReactDOMComponent = require('ReactDOMComponent');
|
||||
|
||||
var mergeInto = require('mergeInto');
|
||||
@@ -51,12 +50,6 @@ function createDOMComponentClass(omitClose, tag) {
|
||||
|
||||
var ConvenienceConstructor = ReactDescriptor.createFactory(Constructor);
|
||||
|
||||
if (__DEV__) {
|
||||
return ReactDescriptorValidator.createFactory(
|
||||
ConvenienceConstructor
|
||||
);
|
||||
}
|
||||
|
||||
return ConvenienceConstructor;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ var ReactComponent = require('ReactComponent');
|
||||
var ReactContext = require('ReactContext');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactDescriptor = require('ReactDescriptor');
|
||||
var ReactDescriptorValidator = require('ReactDescriptorValidator');
|
||||
var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
var ReactErrorUtils = require('ReactErrorUtils');
|
||||
var ReactOwner = require('ReactOwner');
|
||||
@@ -329,17 +328,18 @@ var ReactCompositeComponentInterface = {
|
||||
* which all other static methods are defined.
|
||||
*/
|
||||
var RESERVED_SPEC_KEYS = {
|
||||
displayName: function(Constructor, displayName) {
|
||||
Constructor.displayName = displayName;
|
||||
displayName: function(ConvenienceConstructor, displayName) {
|
||||
ConvenienceConstructor.type.displayName = displayName;
|
||||
},
|
||||
mixins: function(Constructor, mixins) {
|
||||
mixins: function(ConvenienceConstructor, mixins) {
|
||||
if (mixins) {
|
||||
for (var i = 0; i < mixins.length; i++) {
|
||||
mixSpecIntoComponent(Constructor, mixins[i]);
|
||||
mixSpecIntoComponent(ConvenienceConstructor, mixins[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
childContextTypes: function(Constructor, childContextTypes) {
|
||||
childContextTypes: function(ConvenienceConstructor, childContextTypes) {
|
||||
var Constructor = ConvenienceConstructor.type;
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
childContextTypes,
|
||||
@@ -350,7 +350,8 @@ var RESERVED_SPEC_KEYS = {
|
||||
childContextTypes
|
||||
);
|
||||
},
|
||||
contextTypes: function(Constructor, contextTypes) {
|
||||
contextTypes: function(ConvenienceConstructor, contextTypes) {
|
||||
var Constructor = ConvenienceConstructor.type;
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
contextTypes,
|
||||
@@ -358,21 +359,8 @@ var RESERVED_SPEC_KEYS = {
|
||||
);
|
||||
Constructor.contextTypes = merge(Constructor.contextTypes, contextTypes);
|
||||
},
|
||||
/**
|
||||
* Special case getDefaultProps which should move into statics but requires
|
||||
* automatic merging.
|
||||
*/
|
||||
getDefaultProps: function(Constructor, getDefaultProps) {
|
||||
if (Constructor.getDefaultProps) {
|
||||
Constructor.getDefaultProps = createMergedResultFunction(
|
||||
Constructor.getDefaultProps,
|
||||
getDefaultProps
|
||||
);
|
||||
} else {
|
||||
Constructor.getDefaultProps = getDefaultProps;
|
||||
}
|
||||
},
|
||||
propTypes: function(Constructor, propTypes) {
|
||||
propTypes: function(ConvenienceConstructor, propTypes) {
|
||||
var Constructor = ConvenienceConstructor.type;
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
propTypes,
|
||||
@@ -380,8 +368,8 @@ var RESERVED_SPEC_KEYS = {
|
||||
);
|
||||
Constructor.propTypes = merge(Constructor.propTypes, propTypes);
|
||||
},
|
||||
statics: function(Constructor, statics) {
|
||||
mixStaticSpecIntoComponent(Constructor, statics);
|
||||
statics: function(ConvenienceConstructor, statics) {
|
||||
mixStaticSpecIntoComponent(ConvenienceConstructor, statics);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -449,7 +437,7 @@ function validateLifeCycleOnReplaceState(instance) {
|
||||
* Custom version of `mixInto` which handles policy validation and reserved
|
||||
* specification keys when building `ReactCompositeComponent` classses.
|
||||
*/
|
||||
function mixSpecIntoComponent(Constructor, spec) {
|
||||
function mixSpecIntoComponent(ConvenienceConstructor, spec) {
|
||||
invariant(
|
||||
!ReactDescriptor.isValidFactory(spec),
|
||||
'ReactCompositeComponent: You\'re attempting to ' +
|
||||
@@ -461,6 +449,7 @@ function mixSpecIntoComponent(Constructor, spec) {
|
||||
'use a component as a mixin. Instead, just use a regular object.'
|
||||
);
|
||||
|
||||
var Constructor = ConvenienceConstructor.type;
|
||||
var proto = Constructor.prototype;
|
||||
for (var name in spec) {
|
||||
var property = spec[name];
|
||||
@@ -471,7 +460,7 @@ function mixSpecIntoComponent(Constructor, spec) {
|
||||
validateMethodOverride(proto, name);
|
||||
|
||||
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
|
||||
RESERVED_SPEC_KEYS[name](Constructor, property);
|
||||
RESERVED_SPEC_KEYS[name](ConvenienceConstructor, property);
|
||||
} else {
|
||||
// Setup methods on prototype:
|
||||
// The following member methods should not be automatically bound:
|
||||
@@ -532,7 +521,7 @@ function mixSpecIntoComponent(Constructor, spec) {
|
||||
}
|
||||
}
|
||||
|
||||
function mixStaticSpecIntoComponent(Constructor, statics) {
|
||||
function mixStaticSpecIntoComponent(ConvenienceConstructor, statics) {
|
||||
if (!statics) {
|
||||
return;
|
||||
}
|
||||
@@ -542,10 +531,10 @@ function mixStaticSpecIntoComponent(Constructor, statics) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var isInherited = name in Constructor;
|
||||
var isInherited = name in ConvenienceConstructor;
|
||||
var result = property;
|
||||
if (isInherited) {
|
||||
var existingProperty = Constructor[name];
|
||||
var existingProperty = ConvenienceConstructor[name];
|
||||
var existingType = typeof existingProperty;
|
||||
var propertyType = typeof property;
|
||||
invariant(
|
||||
@@ -558,7 +547,8 @@ function mixStaticSpecIntoComponent(Constructor, statics) {
|
||||
);
|
||||
result = createChainedFunction(existingProperty, property);
|
||||
}
|
||||
Constructor[name] = result;
|
||||
ConvenienceConstructor[name] = result;
|
||||
ConvenienceConstructor.type[name] = result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,6 +723,7 @@ var ReactCompositeComponentMixin = {
|
||||
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
|
||||
|
||||
this.context = this._processContext(this._descriptor._context);
|
||||
this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
|
||||
this.props = this._processProps(this.props);
|
||||
|
||||
if (this.__reactAutoBindMap) {
|
||||
@@ -790,6 +781,8 @@ var ReactCompositeComponentMixin = {
|
||||
}
|
||||
this._compositeLifeCycleState = null;
|
||||
|
||||
this._defaultProps = null;
|
||||
|
||||
this._renderedComponent.unmountComponent();
|
||||
this._renderedComponent = null;
|
||||
|
||||
@@ -928,6 +921,12 @@ var ReactCompositeComponentMixin = {
|
||||
*/
|
||||
_processProps: function(newProps) {
|
||||
var props = merge(newProps);
|
||||
var defaultProps = this._defaultProps;
|
||||
for (var propName in defaultProps) {
|
||||
if (typeof props[propName] === 'undefined') {
|
||||
props[propName] = defaultProps[propName];
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
var propTypes = this.constructor.propTypes;
|
||||
if (propTypes) {
|
||||
@@ -946,8 +945,6 @@ var ReactCompositeComponentMixin = {
|
||||
* @private
|
||||
*/
|
||||
_checkPropTypes: function(propTypes, props, location) {
|
||||
// TODO: Stop validating prop types here and only use the descriptor
|
||||
// validation.
|
||||
var componentName = this.constructor.displayName;
|
||||
for (var propName in propTypes) {
|
||||
if (propTypes.hasOwnProperty(propName)) {
|
||||
@@ -1313,11 +1310,16 @@ var ReactCompositeComponent = {
|
||||
Constructor.prototype = new ReactCompositeComponentBase();
|
||||
Constructor.prototype.constructor = Constructor;
|
||||
|
||||
var ConvenienceConstructor = ReactDescriptor.createFactory(Constructor);
|
||||
|
||||
// TODO: Move statics off of the convenience constructor. That way the
|
||||
// factory can be created independently from the main class.
|
||||
|
||||
injectedMixins.forEach(
|
||||
mixSpecIntoComponent.bind(null, Constructor)
|
||||
mixSpecIntoComponent.bind(null, ConvenienceConstructor)
|
||||
);
|
||||
|
||||
mixSpecIntoComponent(Constructor, spec);
|
||||
mixSpecIntoComponent(ConvenienceConstructor, spec);
|
||||
|
||||
invariant(
|
||||
Constructor.prototype.render,
|
||||
@@ -1346,17 +1348,7 @@ var ReactCompositeComponent = {
|
||||
}
|
||||
}
|
||||
|
||||
var descriptorFactory = ReactDescriptor.createFactory(Constructor);
|
||||
|
||||
if (__DEV__) {
|
||||
return ReactDescriptorValidator.createFactory(
|
||||
descriptorFactory,
|
||||
Constructor.propTypes,
|
||||
Constructor.contextTypes
|
||||
);
|
||||
}
|
||||
|
||||
return descriptorFactory;
|
||||
return ConvenienceConstructor;
|
||||
},
|
||||
|
||||
injection: {
|
||||
|
||||
+166
-49
@@ -21,9 +21,165 @@
|
||||
var ReactContext = require('ReactContext');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
|
||||
var merge = require('merge');
|
||||
var monitorCodeUse = require('monitorCodeUse');
|
||||
var warning = require('warning');
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
'react_key_warning': {},
|
||||
'react_numeric_key_warning': {}
|
||||
};
|
||||
var ownerHasMonitoredObjectMap = {};
|
||||
|
||||
var NUMERIC_PROPERTY_REGEX = /^\d+$/;
|
||||
|
||||
/**
|
||||
* Gets the current owner's displayName for use in warnings.
|
||||
*
|
||||
* @internal
|
||||
* @return {?string} Display name or undefined
|
||||
*/
|
||||
function getCurrentOwnerDisplayName() {
|
||||
var current = ReactCurrentOwner.current;
|
||||
return current && current.constructor.displayName || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn if the component doesn't have an explicit key assigned to it.
|
||||
* This component 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.
|
||||
*
|
||||
* @internal
|
||||
* @param {ReactComponent} component Component that requires a key.
|
||||
* @param {*} parentType component's parent's type.
|
||||
*/
|
||||
function validateExplicitKey(component, parentType) {
|
||||
if (component._store.validated || component.props.key != null) {
|
||||
return;
|
||||
}
|
||||
component._store.validated = true;
|
||||
|
||||
warnAndMonitorForKeyUse(
|
||||
'react_key_warning',
|
||||
'Each child in an array should have a unique "key" prop.',
|
||||
component,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn if the key is being defined as an object property but has an incorrect
|
||||
* value.
|
||||
*
|
||||
* @internal
|
||||
* @param {string} name Property name of the key.
|
||||
* @param {ReactComponent} component Component that requires a key.
|
||||
* @param {*} parentType component's parent's type.
|
||||
*/
|
||||
function validatePropertyKey(name, component, parentType) {
|
||||
if (!NUMERIC_PROPERTY_REGEX.test(name)) {
|
||||
return;
|
||||
}
|
||||
warnAndMonitorForKeyUse(
|
||||
'react_numeric_key_warning',
|
||||
'Child objects should have non-numeric keys so ordering is preserved.',
|
||||
component,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared warning and monitoring code for the key warnings.
|
||||
*
|
||||
* @internal
|
||||
* @param {string} warningID The id used when logging.
|
||||
* @param {string} message The base warning that gets output.
|
||||
* @param {ReactComponent} component Component that requires a key.
|
||||
* @param {*} parentType component's parent's type.
|
||||
*/
|
||||
function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
|
||||
var ownerName = getCurrentOwnerDisplayName();
|
||||
var parentName = parentType.displayName;
|
||||
|
||||
var useName = ownerName || parentName;
|
||||
var memoizer = ownerHasKeyUseWarning[warningID];
|
||||
if (memoizer.hasOwnProperty(useName)) {
|
||||
return;
|
||||
}
|
||||
memoizer[useName] = true;
|
||||
|
||||
message += ownerName ?
|
||||
` Check the render method of ${ownerName}.` :
|
||||
` Check the renderComponent call using <${parentName}>.`;
|
||||
|
||||
// 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 childOwnerName = null;
|
||||
if (component._owner && component._owner !== ReactCurrentOwner.current) {
|
||||
// Name of the component that originally created this child.
|
||||
childOwnerName = component._owner.constructor.displayName;
|
||||
|
||||
message += ` It was passed a child from ${childOwnerName}.`;
|
||||
}
|
||||
|
||||
message += ' See http://fb.me/react-warning-keys for more information.';
|
||||
monitorCodeUse(warningID, {
|
||||
component: useName,
|
||||
componentOwner: childOwnerName
|
||||
});
|
||||
console.warn(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log that we're using an object map. We're considering deprecating this
|
||||
* feature and replace it with proper Map and ImmutableMap data structures.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function monitorUseOfObjectMap() {
|
||||
var currentName = getCurrentOwnerDisplayName() || '';
|
||||
if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
|
||||
return;
|
||||
}
|
||||
ownerHasMonitoredObjectMap[currentName] = true;
|
||||
monitorCodeUse('react_object_map_children');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that every component 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 {*} component Statically passed child of any type.
|
||||
* @param {*} parentType component's parent's type.
|
||||
* @return {boolean}
|
||||
*/
|
||||
function validateChildKeys(component, parentType) {
|
||||
if (Array.isArray(component)) {
|
||||
for (var i = 0; i < component.length; i++) {
|
||||
var child = component[i];
|
||||
if (ReactDescriptor.isValidDescriptor(child)) {
|
||||
validateExplicitKey(child, parentType);
|
||||
}
|
||||
}
|
||||
} else if (ReactDescriptor.isValidDescriptor(component)) {
|
||||
// This component was passed in a valid location.
|
||||
component._store.validated = true;
|
||||
} else if (component && typeof component === 'object') {
|
||||
monitorUseOfObjectMap();
|
||||
for (var name in component) {
|
||||
validatePropertyKey(name, component[name], parentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn for mutations.
|
||||
*
|
||||
@@ -81,26 +237,6 @@ function defineMutationMembrane(prototype) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer static properties from the source to the target. Functions are
|
||||
* rebound to have this reflect the original source.
|
||||
*/
|
||||
function proxyStaticMethods(target, source) {
|
||||
if (typeof source !== 'function') {
|
||||
return;
|
||||
}
|
||||
for (var key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
var value = source[key];
|
||||
if (typeof value === 'function') {
|
||||
target[key] = value.bind(source);
|
||||
} else {
|
||||
target[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base constructor for all React descriptors. This is only used to make this
|
||||
* work with a dynamic instanceof check. Nothing should live on this prototype.
|
||||
@@ -118,44 +254,29 @@ ReactDescriptor.createFactory = function(type) {
|
||||
|
||||
var descriptorPrototype = Object.create(ReactDescriptor.prototype);
|
||||
|
||||
var defaultProps = type.getDefaultProps && type.getDefaultProps();
|
||||
|
||||
var factory = function(props, children) {
|
||||
// For consistency we currently allocate a new object for every descriptor.
|
||||
// This protects the descriptor from being mutated by the original props
|
||||
// object being mutated. It also protects the original props object from
|
||||
// being mutated by children arguments and default props. This behavior
|
||||
// comes with a performance cost and could be deprecated in the future.
|
||||
// It could also be optimized with a smarter JSX transform.
|
||||
if (props == null) {
|
||||
props = {};
|
||||
} else if (typeof props === 'object') {
|
||||
props = merge(props);
|
||||
}
|
||||
|
||||
// Children can be more than one argument, and those are transferred onto
|
||||
// the newly allocated props object.
|
||||
// Children can be more than one argument
|
||||
var childrenLength = arguments.length - 1;
|
||||
if (childrenLength === 1) {
|
||||
if (__DEV__) {
|
||||
validateChildKeys(children, type);
|
||||
}
|
||||
props.children = children;
|
||||
} else if (childrenLength > 1) {
|
||||
var childArray = Array(childrenLength);
|
||||
for (var i = 0; i < childrenLength; i++) {
|
||||
if (__DEV__) {
|
||||
validateChildKeys(arguments[i + 1], type);
|
||||
}
|
||||
childArray[i] = arguments[i + 1];
|
||||
}
|
||||
props.children = childArray;
|
||||
}
|
||||
|
||||
// Resolve default props
|
||||
if (defaultProps) {
|
||||
for (var propName in defaultProps) {
|
||||
if (typeof props[propName] === 'undefined') {
|
||||
props[propName] = defaultProps[propName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the descriptor object
|
||||
var descriptor = Object.create(descriptorPrototype);
|
||||
|
||||
// Record the component responsible for creating this descriptor.
|
||||
@@ -168,13 +289,10 @@ ReactDescriptor.createFactory = function(type) {
|
||||
if (__DEV__) {
|
||||
// The validation flag and props are currently mutative. We put them on
|
||||
// an external backing store so that we can freeze the whole object.
|
||||
// This can be replaced with a WeakMap once they are implemented in
|
||||
// commonly used development environments.
|
||||
descriptor._store = { validated: false, props: props };
|
||||
|
||||
// We're not allowed to set props directly on the object so we early
|
||||
// return and rely on the prototype membrane to forward to the backing
|
||||
// store.
|
||||
// return and rely on the prototype membrane toward to the backing store.
|
||||
if (useMutationMembrane) {
|
||||
Object.freeze(descriptor);
|
||||
return descriptor;
|
||||
@@ -183,6 +301,7 @@ ReactDescriptor.createFactory = function(type) {
|
||||
|
||||
descriptor.props = props;
|
||||
return descriptor;
|
||||
|
||||
};
|
||||
|
||||
// Currently we expose the prototype of the descriptor so that
|
||||
@@ -197,8 +316,6 @@ ReactDescriptor.createFactory = function(type) {
|
||||
factory.type = type;
|
||||
descriptorPrototype.type = type;
|
||||
|
||||
proxyStaticMethods(factory, type);
|
||||
|
||||
// Expose a unique constructor on the prototype is that this works with type
|
||||
// systems that compare constructor properties: <Foo />.constructor === Foo
|
||||
// This may be controversial since it requires a known factory function.
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
/**
|
||||
* Copyright 2014 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactDescriptorValidator
|
||||
*/
|
||||
|
||||
/**
|
||||
* ReactDescriptorValidator provides a wrapper around a descriptor factory
|
||||
* which validates the props passed to the descriptor. 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 ReactDescriptor = require('ReactDescriptor');
|
||||
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
|
||||
var monitorCodeUse = require('monitorCodeUse');
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
'react_key_warning': {},
|
||||
'react_numeric_key_warning': {}
|
||||
};
|
||||
var ownerHasMonitoredObjectMap = {};
|
||||
|
||||
var loggedTypeFailures = {};
|
||||
|
||||
var NUMERIC_PROPERTY_REGEX = /^\d+$/;
|
||||
|
||||
/**
|
||||
* Gets the current owner's displayName for use in warnings.
|
||||
*
|
||||
* @internal
|
||||
* @return {?string} Display name or undefined
|
||||
*/
|
||||
function getCurrentOwnerDisplayName() {
|
||||
var current = ReactCurrentOwner.current;
|
||||
return current && current.constructor.displayName || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn if the component doesn't have an explicit key assigned to it.
|
||||
* This component 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.
|
||||
*
|
||||
* @internal
|
||||
* @param {ReactComponent} component Component that requires a key.
|
||||
* @param {*} parentType component's parent's type.
|
||||
*/
|
||||
function validateExplicitKey(component, parentType) {
|
||||
if (component._store.validated || component.props.key != null) {
|
||||
return;
|
||||
}
|
||||
component._store.validated = true;
|
||||
|
||||
warnAndMonitorForKeyUse(
|
||||
'react_key_warning',
|
||||
'Each child in an array should have a unique "key" prop.',
|
||||
component,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn if the key is being defined as an object property but has an incorrect
|
||||
* value.
|
||||
*
|
||||
* @internal
|
||||
* @param {string} name Property name of the key.
|
||||
* @param {ReactComponent} component Component that requires a key.
|
||||
* @param {*} parentType component's parent's type.
|
||||
*/
|
||||
function validatePropertyKey(name, component, parentType) {
|
||||
if (!NUMERIC_PROPERTY_REGEX.test(name)) {
|
||||
return;
|
||||
}
|
||||
warnAndMonitorForKeyUse(
|
||||
'react_numeric_key_warning',
|
||||
'Child objects should have non-numeric keys so ordering is preserved.',
|
||||
component,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared warning and monitoring code for the key warnings.
|
||||
*
|
||||
* @internal
|
||||
* @param {string} warningID The id used when logging.
|
||||
* @param {string} message The base warning that gets output.
|
||||
* @param {ReactComponent} component Component that requires a key.
|
||||
* @param {*} parentType component's parent's type.
|
||||
*/
|
||||
function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
|
||||
var ownerName = getCurrentOwnerDisplayName();
|
||||
var parentName = parentType.displayName;
|
||||
|
||||
var useName = ownerName || parentName;
|
||||
var memoizer = ownerHasKeyUseWarning[warningID];
|
||||
if (memoizer.hasOwnProperty(useName)) {
|
||||
return;
|
||||
}
|
||||
memoizer[useName] = true;
|
||||
|
||||
message += ownerName ?
|
||||
` Check the render method of ${ownerName}.` :
|
||||
` Check the renderComponent call using <${parentName}>.`;
|
||||
|
||||
// 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 childOwnerName = null;
|
||||
if (component._owner && component._owner !== ReactCurrentOwner.current) {
|
||||
// Name of the component that originally created this child.
|
||||
childOwnerName = component._owner.constructor.displayName;
|
||||
|
||||
message += ` It was passed a child from ${childOwnerName}.`;
|
||||
}
|
||||
|
||||
message += ' See http://fb.me/react-warning-keys for more information.';
|
||||
monitorCodeUse(warningID, {
|
||||
component: useName,
|
||||
componentOwner: childOwnerName
|
||||
});
|
||||
console.warn(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log that we're using an object map. We're considering deprecating this
|
||||
* feature and replace it with proper Map and ImmutableMap data structures.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function monitorUseOfObjectMap() {
|
||||
var currentName = getCurrentOwnerDisplayName() || '';
|
||||
if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
|
||||
return;
|
||||
}
|
||||
ownerHasMonitoredObjectMap[currentName] = true;
|
||||
monitorCodeUse('react_object_map_children');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that every component 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 {*} component Statically passed child of any type.
|
||||
* @param {*} parentType component's parent's type.
|
||||
* @return {boolean}
|
||||
*/
|
||||
function validateChildKeys(component, parentType) {
|
||||
if (Array.isArray(component)) {
|
||||
for (var i = 0; i < component.length; i++) {
|
||||
var child = component[i];
|
||||
if (ReactDescriptor.isValidDescriptor(child)) {
|
||||
validateExplicitKey(child, parentType);
|
||||
}
|
||||
}
|
||||
} else if (ReactDescriptor.isValidDescriptor(component)) {
|
||||
// This component was passed in a valid location.
|
||||
component._store.validated = true;
|
||||
} else if (component && typeof component === 'object') {
|
||||
monitorUseOfObjectMap();
|
||||
for (var name in component) {
|
||||
validatePropertyKey(name, component[name], parentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the props are valid
|
||||
*
|
||||
* @param {string} componentName Name of the component for error messages.
|
||||
* @param {object} propTypes Map of prop name to a ReactPropType
|
||||
* @param {object} props
|
||||
* @param {string} location e.g. "prop", "context", "child context"
|
||||
* @private
|
||||
*/
|
||||
function checkPropTypes(componentName, propTypes, props, location) {
|
||||
for (var propName in propTypes) {
|
||||
if (propTypes.hasOwnProperty(propName)) {
|
||||
var error;
|
||||
// Prop type validation may throw. In case they do, we don't want to
|
||||
// fail the render phase where it didn't fail before. So we log it.
|
||||
// After these have been cleaned up, we'll let them throw.
|
||||
try {
|
||||
error = propTypes[propName](props, propName, componentName, location);
|
||||
} catch (ex) {
|
||||
error = ex;
|
||||
}
|
||||
if (error instanceof Error && !(error.message in loggedTypeFailures)) {
|
||||
// Only monitor this failure once because there tends to be a lot of the
|
||||
// same error.
|
||||
loggedTypeFailures[error.message] = true;
|
||||
// This will soon use the warning module
|
||||
monitorCodeUse(
|
||||
'react_failed_descriptor_type_check',
|
||||
{ message: error.message }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ReactDescriptorValidator = {
|
||||
|
||||
/**
|
||||
* Wraps a descriptor factory function in another function which validates
|
||||
* the props and context of the descriptor and warns about any failed type
|
||||
* checks.
|
||||
*
|
||||
* @param {function} factory The original descriptor factory
|
||||
* @param {object?} propTypes A prop type definition set
|
||||
* @param {object?} contextTypes A context type definition set
|
||||
* @return {object} The component descriptor, which may be invalid.
|
||||
* @private
|
||||
*/
|
||||
createFactory: function(factory, propTypes, contextTypes) {
|
||||
var validatedFactory = function(props, children) {
|
||||
var descriptor = factory.apply(this, arguments);
|
||||
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
validateChildKeys(arguments[i], descriptor.type);
|
||||
}
|
||||
|
||||
var name = descriptor.type.displayName;
|
||||
if (propTypes) {
|
||||
checkPropTypes(
|
||||
name,
|
||||
propTypes,
|
||||
descriptor.props,
|
||||
ReactPropTypeLocations.prop
|
||||
);
|
||||
}
|
||||
if (contextTypes) {
|
||||
checkPropTypes(
|
||||
name,
|
||||
contextTypes,
|
||||
descriptor._context,
|
||||
ReactPropTypeLocations.context
|
||||
);
|
||||
}
|
||||
return descriptor;
|
||||
};
|
||||
|
||||
validatedFactory.prototype = factory.prototype;
|
||||
validatedFactory.type = factory.type;
|
||||
|
||||
// Copy static properties
|
||||
for (var key in factory) {
|
||||
if (factory.hasOwnProperty(key)) {
|
||||
validatedFactory[key] = factory[key];
|
||||
}
|
||||
}
|
||||
|
||||
return validatedFactory;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactDescriptorValidator;
|
||||
@@ -614,7 +614,7 @@ describe('Custom validator', function() {
|
||||
var instance = <Component num={5} />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
|
||||
expect(spy.argsForCall.length).toBe(2); // temp double validation
|
||||
expect(spy.argsForCall.length).toBe(1);
|
||||
expect(spy.argsForCall[0][1]).toBe('num');
|
||||
expect(spy.argsForCall[0][2]).toBe('Component');
|
||||
});
|
||||
@@ -632,7 +632,7 @@ describe('Custom validator', function() {
|
||||
var instance = <Component bla={5} />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
|
||||
expect(spy.argsForCall.length).toBe(2); // temp double validation
|
||||
expect(spy.argsForCall.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should have received the validator\'s return value', function() {
|
||||
|
||||
Reference in New Issue
Block a user