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.
304 lines
8.1 KiB
304 lines
8.1 KiB
/** |
|
* Copyright 2014-2015, Facebook, Inc. |
|
* All rights reserved. |
|
* |
|
* This source code is licensed under the BSD-style license found in the |
|
* LICENSE file in the root directory of this source tree. An additional grant |
|
* of patent rights can be found in the PATENTS file in the same directory. |
|
* |
|
* @providesModule ReactElement |
|
*/ |
|
|
|
'use strict'; |
|
|
|
var ReactContext = require("./ReactContext"); |
|
var ReactCurrentOwner = require("./ReactCurrentOwner"); |
|
|
|
var assign = require("./Object.assign"); |
|
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) { |
|
("production" !== process.env.NODE_ENV ? warning( |
|
false, |
|
'Don\'t set the %s property of the React element. Instead, ' + |
|
'specify the correct value when initially creating the element.', |
|
key |
|
) : null); |
|
this._store[key] = value; |
|
} |
|
|
|
}); |
|
} |
|
|
|
/** |
|
* This is updated to true if the membrane is successfully created. |
|
*/ |
|
var useMutationMembrane = false; |
|
|
|
/** |
|
* Warn for mutations. |
|
* |
|
* @internal |
|
* @param {object} element |
|
*/ |
|
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 elements. 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 |
|
* @param {*} props |
|
* @internal |
|
*/ |
|
var ReactElement = function(type, key, ref, owner, context, props) { |
|
// Built-in properties that belong on the element |
|
this.type = type; |
|
this.key = key; |
|
this.ref = ref; |
|
|
|
// Record the component responsible for creating this element. |
|
this._owner = owner; |
|
|
|
// TODO: Deprecate withContext, and then the context becomes accessible |
|
// through the owner. |
|
this._context = context; |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
// 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 = {props: props, originalProps: assign({}, props)}; |
|
|
|
// To make comparing ReactElements easier for testing purposes, we make |
|
// the validation flag non-enumerable (where possible, which should |
|
// include every environment we run tests in), so the test framework |
|
// ignores it. |
|
try { |
|
Object.defineProperty(this._store, 'validated', { |
|
configurable: false, |
|
enumerable: false, |
|
writable: true |
|
}); |
|
} catch (x) { |
|
} |
|
this._store.validated = false; |
|
|
|
// 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; |
|
}; |
|
|
|
// We intentionally don't expose the function on the constructor property. |
|
// ReactElement should be indistinguishable from a plain object. |
|
ReactElement.prototype = { |
|
_isReactElement: true |
|
}; |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
defineMutationMembrane(ReactElement.prototype); |
|
} |
|
|
|
ReactElement.createElement = function(type, config, children) { |
|
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 && type.defaultProps) { |
|
var defaultProps = type.defaultProps; |
|
for (propName in defaultProps) { |
|
if (typeof props[propName] === 'undefined') { |
|
props[propName] = defaultProps[propName]; |
|
} |
|
} |
|
} |
|
|
|
return new ReactElement( |
|
type, |
|
key, |
|
ref, |
|
ReactCurrentOwner.current, |
|
ReactContext.current, |
|
props |
|
); |
|
}; |
|
|
|
ReactElement.createFactory = function(type) { |
|
var factory = ReactElement.createElement.bind(null, type); |
|
// Expose the type on the factory and the prototype so that it can be |
|
// easily accessed on elements. E.g. <Foo />.type === Foo.type. |
|
// This should not be named `constructor` since this may not be the function |
|
// that created the element, and it may not even be a constructor. |
|
// Legacy hook TODO: Warn if this is accessed |
|
factory.type = type; |
|
return factory; |
|
}; |
|
|
|
ReactElement.cloneAndReplaceProps = function(oldElement, newProps) { |
|
var newElement = new ReactElement( |
|
oldElement.type, |
|
oldElement.key, |
|
oldElement.ref, |
|
oldElement._owner, |
|
oldElement._context, |
|
newProps |
|
); |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
// If the key on the original is valid, then the clone is valid |
|
newElement._store.validated = oldElement._store.validated; |
|
} |
|
return newElement; |
|
}; |
|
|
|
ReactElement.cloneElement = function(element, config, children) { |
|
var propName; |
|
|
|
// Original props are copied |
|
var props = assign({}, element.props); |
|
|
|
// Reserved names are extracted |
|
var key = element.key; |
|
var ref = element.ref; |
|
|
|
// Owner will be preserved, unless ref is overridden |
|
var owner = element._owner; |
|
|
|
if (config != null) { |
|
if (config.ref !== undefined) { |
|
// Silently steal the ref from the parent. |
|
ref = config.ref; |
|
owner = ReactCurrentOwner.current; |
|
} |
|
if (config.key !== undefined) { |
|
key = '' + config.key; |
|
} |
|
// Remaining properties override existing props |
|
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; |
|
} |
|
|
|
return new ReactElement( |
|
element.type, |
|
key, |
|
ref, |
|
owner, |
|
element._context, |
|
props |
|
); |
|
}; |
|
|
|
/** |
|
* @param {?object} object |
|
* @return {boolean} True if `object` is a valid component. |
|
* @final |
|
*/ |
|
ReactElement.isValidElement = function(object) { |
|
// ReactTestUtils is often used outside of beforeEach where as React is |
|
// within it. This leads to two different instances of React on the same |
|
// page. To identify a element from a different React instance we use |
|
// a flag instead of an instanceof check. |
|
var isElement = !!(object && object._isReactElement); |
|
// if (isElement && !(object instanceof ReactElement)) { |
|
// This is an indicator that you're using multiple versions of React at the |
|
// same time. This will screw with ownership and stuff. Fix it, please. |
|
// TODO: We could possibly warn here. |
|
// } |
|
return isElement; |
|
}; |
|
|
|
module.exports = ReactElement;
|
|
|