前言
本文着重分析在React源码浅入(一)中提到的两个函数:ReactElement.createElement 和 ReactElement.cloneElement,从上面两个方法,我们可以看到他们都是出自与ReactElement这个对象。那么,先大体看下ReactElement这个对象里有什么:
- createElement
- createFactory
- cloneAndReplaceKey
- cloneElement
回顾下之前的调用方式:
ReactElement.createElement.apply(this, arguments)复制代码
那我们先来看下,这个arguments是何方神圣,console打出来之后:
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) {
// 根据config,设置ref、key、self、source。 config相当于props。
...
}
// Children can be more than one argument,and those are
// transferred onto the newly allocated props object.
// 目的:将children赋值给props.
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;
}
// 从上述代码来看,children.length === 1的时候,props.children是个object,
// children.length > 1的时候,是个数组。当参数children没传的时候,props.children是undefined,所以说阮一峰老师说的这句话:
// React 提供一个工具方法React.Children.map来处理 this.props.children 。
// 我们可以用React.Children.map 来遍历子节点,而不用担心 this.props.children
// 的数据类型是 undefined 还是 object
// 分配默认属性,不包含保留字段,比如ref、key等。
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
// 设置key、ref的configurable,表示可delete该属性,可重置该属性。详细关于描述符的解释,
// 可参照https://segmentfault.com/a/1190000007434923
if ("development" !== 'production') {
// 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 () {
return undefined;
},
configurable: true
});
}
if (!props.hasOwnProperty('ref')) {
Object.defineProperty(props, 'ref', {
get: function () {
return undefined;
},
configurable: true
});
}
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};复制代码
ReactCurrentOwner 所有代码如下:
'use strict';
/**
* Keeps track of the current owner.
*
* The current owner is the component who should own any components that are
* currently being constructed.
*/
var ReactCurrentOwner = {
/**
* @internal
* @type {ReactComponent}
*/
current: null
};复制代码
createElement方法最终调用的是ReactElement。我们再来看看ReactElement这个函数。首先看下这个函数上面的注释:
/**
* Factory method to create a new React element. This no longer adheres to
* the class pattern, so do not use new to call it. Also, no instanceof check
* will work. Instead test $$typeof field against Symbol.for('react.element') to check
* if something is a React Element.
*/复制代码
大体翻译是工厂模式,创建新的React元素。这里不在运用类模式创建,所以不需要new一个实例出来,因此,也没有instance of方法。取而代之,用 $$typeof === == REACT_ELEMENT_TYPE ( REACT_ELEMENT_TYPE=Symbol.for('react.element'))来判断是否是react元素。
获取到的关键信息如下:
- Factory method工厂模式,不需要new实例。故没有 instanceof 方法
- $$typeof === == REACT_ELEMENT_TYPE来判断是否是react元素
最后返回的element如下(一个稍微复杂的):
好了,这就是传说中的Virtual Dom,React中的React Node了。从途中,能看到_owner这个属性,是个ReactCompositeComponentWrapper 这个东西!!!!这是个什么东西,咱们后续再说。
好了,总结一下createElement的目的,将一组属性,组成一个对象。
#ReactElement.cloneElement
ReactElement.cloneElement 和ReactElement.createElement 大同小异,赋值属性,调用ReactElement函数。不同的一点,只是,属性的与config转入的合并,且config的优先级 > clone的element
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;
// 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);
};复制代码