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:
Paul O’Shannessy
2014-05-06 09:09:51 -07:00
parent ff52e3df00
commit 88c94683d6
5 changed files with 205 additions and 386 deletions
-7
View File
@@ -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;
}
+37 -45
View File
@@ -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
View File
@@ -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.
-283
View File
@@ -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;
+2 -2
View File
@@ -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() {