React中createElement和ReactElement
库或框架用多了,如果只是熟记一些API或一些坑的话,也就太没意思了,不如来了解下API背后的原理。首先来看下JSX转义成了什么以及他做了啥。
JSX
首先从我们常用的JSX来看吧。来看段React官网上的很简单的demo吧,
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
在JSX中,创建了一个h1标签。我们都知道这JSX语法在浏览器中是识别不了的,借助babel,将这段代码转化下:
可以看到JSX部分<h1>Hello, {name}</h1>
已经被转换成React.createElement("h1", null, "Hello, ", name)
了,而该函数方法浏览器是可以识别的。
注:这也就是为什么明明只写了个很简单的JSX,在顶部依然需要写import React from 'react'
。
我们将DOM稍微复杂些,并增加些属性:
return (
<div className='123' id='what'>
<div><span>123</span></div>
<span>456</span>
</div>
)
// 转换
return React.createElement("div", {
className: "123",
id: "what"
},
React.createElement("div", null, React.createElement("span", null, "123")),
React.createElement("span", null, "456")
);
有层级关系的会在第三个之后的参数叠起来。
React.createElement
带着React.createElement方法是啥的疑问,看React的源码。首先先来到packages/react/src/React.js
文件,该文件最终导出了React对象,找到他的createElement方法,可以见到:
createElement: __DEV__ ? createElementWithValidation : createElement
_DEV_ 表示是否是开发环境下,当在开发环境下时,使用了createElementWithValidation
函数,它位于packages/react/src/ReactElementValidator.js
文件中,createElement
函数在packages/react/src/ReactElement.js
。
createElementWithValidation
我们来看下createElementWithValidation
方法,
const validType = isValidElementType(type);
if (!validType) {
// ......
}
isValidElementType
方法主要用来检测传入参数是否是个有效的React元素类型。如果不是的话,会执行if中的内容,附加一些检测和warning提示。
const element = createElement.apply(this, arguments);
if (element == null) {
return element;
}
随后利用createElement创建ReactElement,这部分到createElement.js
再叙述。
if (validType) {
for (let i = 2; i < arguments.length; i++) {
validateChildKeys(arguments[i], type);
}
}
刚才在第一部分中,尝试了增加多个子元素,都是依次从第三个元素开始,此处循环从2开始就是为了拿到所有的子元素。
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
return element;
如果ReactElement是Fragment类型的,需要特殊处理下。
在validateFragmentProps
函数下,针对在开发模式下,对Fragment元素做属性检测,提示只能有children和key属性,并且如果定义了ref属性,也会作出警告。
而validatePropTypes
函数则是用于检查propTypes的,该方法仅在开发模式下执行,他检测类组件或者函数组件设置默认prop只能用defaultProps,getDefaultProps只能用于React.createClass中。最后返回这个ReactElement。
createElement
该方法返回一个制定type类型的ReactElement。
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + 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 (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
config就是JSX元素上的一些属性,hasValidRef
方法判断ref属性是否正确,方法hasValidKey
则是去判断key属性是否合法,并将key转成字符串保存。
for循环则将元素上的非RESERVED_PROPS中的属性都保存到props上。
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
childrenLength获取child元素的长度,当长度为1时,则直接赋值到props的children下。当长度大于1时,将子元素存到数组中,再赋值到children属性下。
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
这块是对组件设置默认的props值,比如某些非必传的参数,此时他为undefined,而defaultProps中的值就赋值过去了。
if (__DEV__) {
// ....
}
开发模式下,将key和ref通过defineProperty绑定到props下,并设置他的getter函数,如果直接获取该key和ref值时,则会作出相应的警告提示。
最后,返回了ReactElement函数。
React.ReactElement
ReactElement方法返回react元素,所以$$typeof被设置成了REACT_ELEMENT_TYPE,当react元素渲染到DOM上时,也需要判断$$typeof===REACT_ELEMENT_TYPE。
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
};
- type,是createElement方法中的第一个参数,通常是原生的DOM字符串或组件元素。
- key,是元素上的key属性值。
- ref,是元素上的ref属性值。
- props,是在createElement中提取的props。
- _owner,记录该元素的创建者。
if (__DEV__) {
element._store = {};
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
开发模式下,会对_store增加validated、_self、_source属性,不过目前不知道这些有啥用。
return element;
最后返回了该element对象。