ReactElement算是React源码中比较简单的部分了,直接看源码:
var ReactElement = function(type, key, ref, self, source, owner, props) { var element = { // This tag allow us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, // Built-in properties that belong on the element type: type, key: key, ref: ref, props: props, // Record the component responsible for creating this element. _owner: owner, }; if (__DEV__) { // The validation flag is currently mutative. We put it 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. element._store = {}; // 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. if (canDefineProperty) { Object.defineProperty(element._store, 'validated', { configurable: false, enumerable: false, writable: true, value: false, }); // self and source are DEV only properties. Object.defineProperty(element, '_self', { configurable: false, enumerable: false, writable: false, value: self, }); // Two elements created in two different places should be considered // equal for testing purposes and therefore we hide it from enumeration. Object.defineProperty(element, '_source', { configurable: false, enumerable: false, writable: false, value: source, }); } else { element._store.validated = false; element._self = self; element._source = source; } if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); } } return element; };
可以看出ReactElement就是一个函数,传入一系列参数作为一个element对象的属性,然后再把这个对象return出来,但是注意到有一个属性是$$typeof: REACT_ELEMENT_TYPE,然后我查找了一下这个REACT_ELEMENT_TYPE的定义:
// The Symbol used to tag the ReactElement type. If there is no native Symbol // nor polyfill, then a plain number is used for performance. var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) || 0xeac7;
根据注释可以看出这个属性是用来标记当前对象是一个ReactElement,如果不是原生Symbol类型或者填充,则使用普通数字来代替,这也区分了element并非普通对象,而是特殊的ReactElement。
接下来我们再来看一下ReactElement.createElement这个方法,还是直接来看源码:
ReactElement.createElement = function(type, config, children) { var propName; // Reserved names are extracted var props = {}; var key = null; var ref = null; var self = null; var source = null; if (config != null) { if (__DEV__) { ref = !config.hasOwnProperty('ref') || Object.getOwnPropertyDescriptor(config, 'ref').get ? null : config.ref; key = !config.hasOwnProperty('key') || Object.getOwnPropertyDescriptor(config, 'key').get ? null : '' + config.key; } else { ref = config.ref === undefined ? null : config.ref; key = config.key === undefined ? null : '' + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // 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 (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } if (__DEV__) { // Create dummy `key` and `ref` property to `props` to warn users // against its use if (typeof props.$$typeof === 'undefined' || props.$$typeof !== REACT_ELEMENT_TYPE) { if (!props.hasOwnProperty('key')) { Object.defineProperty(props, 'key', { get: function() { if (!specialPropKeyWarningShown) { specialPropKeyWarningShown = true; warning( false, '%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', typeof type === 'function' && 'displayName' in type ? type.displayName : 'Element' ); } return undefined; }, configurable: true, }); } if (!props.hasOwnProperty('ref')) { Object.defineProperty(props, 'ref', { get: function() { if (!specialPropRefWarningShown) { specialPropRefWarningShown = true; warning( false, '%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', typeof type === 'function' && 'displayName' in type ? type.displayName : 'Element' ); } return undefined; }, configurable: true, }); } } } return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props ); };
有点长,但其实也很简单,总结一下就是四点:
1,如果有config参数,那么就把config内的同名ReactElement属性给替换掉;
2,如果arguments.length为3,表示有第三个参数children,并且children只有一个,那么直接进行赋值同名属性children,如果大于3,表示children不止一个,就直接for循环赋值;
3,如果存在type参数并且有defaultprops静态变量,那么就循环defaultProps的属性,只要属性名不为undefined,就赋值给props同名属性;
4,不允许向props添加key和ref属性;
最后返回创建的ReactElement。
还有一个常用的是React.cloneElement:
ReactElement.cloneElement = function(element, config, children) { var propName; // Original props are copied var props = Object.assign({}, element.props); // Reserved names are extracted var key = element.key; var ref = element.ref; // Self is preserved since the owner is preserved. var self = element._self; // Source is preserved since cloneElement is unlikely to be targeted by a // transpiler, and the original source is probably a better indicator of the // true owner. var source = element._source; // 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 var defaultProps; if (element.type && element.type.defaultProps) { defaultProps = element.type.defaultProps; } for (propName in config) { if (config.hasOwnProperty(propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { if (config[propName] === undefined && defaultProps !== undefined) { // Resolve default props props[propName] = defaultProps[propName]; } else { 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 ReactElement( element.type, key, ref, self, source, owner, props ); };
如果config里面有ref,key,props,则全部替换掉config里面的,同名的props直接覆盖,同时参数的children,也作为新的children,最后返回赋值后的ReactElement;
最后还有一个检测ReactElement是否为有效的方法ReactElement.isValidElement:
/** * @param {?object} object * @return {boolean} True if `object` is a valid component. * @final */ ReactElement.isValidElement = function(object) { return ( typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE ); };
而这个REACT_ELEMENT_TYPE前面已经介绍过了,这里不再多说。
1