mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
271 lines
7.1 KiB
JavaScript
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;
|