Files
react/src/core/ReactDescriptor.js
T
Sebastian Markbage c419cce5c9 Move defaultProps resolution to the descriptor factory
Moves the defaultProps resolution to the descriptor factory.
2014-07-18 22:01:36 -07:00

271 lines
7.1 KiB
JavaScript

/**
* 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 ReactDescriptor
*/
"use strict";
var ReactContext = require('ReactContext');
var ReactCurrentOwner = require('ReactCurrentOwner');
var warning = require('warning');
var RESERVED_PROPS = {
key: true,
ref: true
};
/**
* Warn for mutations.
*
* @internal
* @param {object} object
* @param {string} key
*/
function defineWarningProperty(object, key) {
Object.defineProperty(object, key, {
configurable: false,
enumerable: true,
get: function() {
if (!this._store) {
return null;
}
return this._store[key];
},
set: function(value) {
warning(
false,
'Don\'t set the ' + key + ' property of the component. ' +
'Mutate the existing props object instead.'
);
this._store[key] = value;
}
});
}
/**
* This is updated to true if the membrane is successfully created.
*/
var useMutationMembrane = false;
/**
* Warn for mutations.
*
* @internal
* @param {object} descriptor
*/
function defineMutationMembrane(prototype) {
try {
var pseudoFrozenProperties = {
props: true
};
for (var key in pseudoFrozenProperties) {
defineWarningProperty(prototype, key);
}
useMutationMembrane = true;
} catch (x) {
// IE will fail on defineProperty
}
}
/**
* 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.
*
* @param {*} type
* @param {string|object} ref
* @param {*} key
* @params {*} props
* @internal
*/
var ReactDescriptor = function(type, key, ref, owner, context, props) {
// Built-in properties that belong on the descriptor
this.type = type;
this.key = key;
this.ref = ref;
// Record the component responsible for creating this descriptor.
this._owner = owner;
// TODO: Deprecate withContext, and then the context becomes accessible
// through the owner.
this._context = context;
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.
this._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.
if (useMutationMembrane) {
Object.freeze(this);
return;
}
}
this.props = props;
};
if (__DEV__) {
defineMutationMembrane(ReactDescriptor.prototype);
}
ReactDescriptor.createDescriptor = function(type, config, children) {
if (type.isReactLegacyFactory) {
// This is probably a legacy factory created by ReactCompositeComponent.
// We unwrap it to get to the underlying class.
// TODO: Drop this check when we drop ReactLegacyDescriptor.
type = type.type;
}
if (typeof type === 'function') {
var isReactClass =
typeof type.prototype.mountComponent === 'function' &&
typeof type.prototype.receiveComponent === 'function';
if (!isReactClass) {
// This is being called with a plain function we should invoke it
// immediately as if this was used with legacy JSX.
if (__DEV__) {
warning(
false,
'This JSX uses a plain function. Only React components are valid in' +
'JSX.'
);
}
var args = Array.prototype.slice.call(arguments, 1);
return type.apply(null, args);
}
}
var propName;
// Reserved names are extracted
var props = {};
var key = null;
var ref = null;
if (config != null) {
ref = config.ref === undefined ? null : config.ref;
key = config.key === undefined ? null : '' + config.key;
// Remaining properties are added to a new props object
for (propName in config) {
if (config.hasOwnProperty(propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
// Resolve default props
if (type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (typeof props[propName] === 'undefined') {
props[propName] = defaultProps[propName];
}
}
}
return new ReactDescriptor(
type,
key,
ref,
ReactCurrentOwner.current,
ReactContext.current,
props
);
};
ReactDescriptor.createFactory = function(type) {
if (type.isReactLegacyFactory) {
// This is probably a legacy factory created by ReactCompositeComponent.
// We unwrap it to get to the underlying class.
// TODO: Drop this check when we drop ReactLegacyDescriptor.
type = type.type;
}
var factory = ReactDescriptor.createDescriptor.bind(null, type);
// Expose the type on the factory and the prototype so that it can be
// easily accessed on descriptors. E.g. <Foo />.type === Foo.type.
// This should not be named `constructor` since this may not be the function
// that created the descriptor, and it may not even be a constructor.
factory.type = type;
return factory;
};
ReactDescriptor.cloneAndReplaceProps = function(oldDescriptor, newProps) {
var newDescriptor = new ReactDescriptor(
oldDescriptor.type,
oldDescriptor.key,
oldDescriptor.ref,
oldDescriptor._owner,
oldDescriptor._context,
newProps
);
if (__DEV__) {
// If the key on the original is valid, then the clone is valid
newDescriptor._store.validated = oldDescriptor._store.validated;
}
return newDescriptor;
};
/**
* Checks if a value is a valid descriptor constructor.
*
* @param {*}
* @return {boolean}
* @public
*/
ReactDescriptor.isValidFactory = function(factory) {
return typeof factory === 'function' &&
typeof factory.type === 'function' &&
typeof factory.type.prototype.mountComponent === 'function' &&
typeof factory.type.prototype.receiveComponent === 'function';
};
/**
* @param {?object} object
* @return {boolean} True if `object` is a valid component.
* @final
*/
ReactDescriptor.isValidDescriptor = function(object) {
return object instanceof ReactDescriptor;
};
module.exports = ReactDescriptor;