在上一篇说过,React创建元素有两种方式:
第一种是通过JSX方式创建,第二种是通过React.createElement方法创建。但是通过JSX创建React元素的方式,最终也会经过babel进行转换,再用React.createElement进行元素创建。
而这一篇文章,主要是讲一下React.createElement是如何创建React元素的。
为了了解React.createElement这个方法,我们自己手动去实现一个简易版的。
OK,首先我们通过React.createElement方法创建一个React元素,并且打印出来:
const b = React.createElement('div',{data: '1234'},'oneTwo')
console.log(b);
打印出来的结果为:
所以,如果我们要实现出来React.createElement这个方法,定义的时候我们可以这么写:
function createElement(type, config, children){
let props = {}
let myType = ''
let key = ''
let ref = ''
return {
$$typeOf: 'react.element',
type: myType,
props,
key,
ref
}
}
这里我们不管**$$typeOf**这个变量,只考虑其他的变量,最终返回的对象应该是这个数据结构。
好了,方法的定义已经写完了。针对于传进来的这三个参数,我们一个一个看。
第一个参数type,它代表的是创建React元素的类型。
这里面可以是传统的HTML元素,例如div,h1,span等标签。
也可以是一个React组件(注意这里哦)。
而React创建组件又有两种方式,所以type的值,有三种数据类型:
(1)字符串:例如"div",“h1”,“span”,"p"等标签
(2)函数:通过函数的方式创建React组件
(3)类:通过class的方式创建React组件
而这个时候就有一个问题!
class Demo {
}
typeof Demo
上面的值应该为什么呢?答案是function,所以在这里我们只需要考虑type为string和function两种类型即可。
所以我们可以写一个判断类型的方法:
function isValidElementType(type){
if(typeof type === 'string' || typeof type === 'function'){
return true;
}
return false;
}
在我们的主方法里面引用:
function createElement(type, config, children){
let props = {}
let myType = ''
let key = ''
let ref = ''
if(isValidElementType(type)){
myType = type;
}
return {
$$typeOf: 'react.element',
type: myType,
props,
key,
ref
}
}
对于React.createElement方法的第二个参数,接收看一个对象。而对象下所有的属性,都会被放在props里面。
这句话对不对呢?其实也不是很对,是有特例的,如果在这个对象下,有key或者ref这两个属性。它是不会被放在props里面的,而是直接赋值给key和ref
Ok,有了上面的话,我们就可以对config进行处理了:
if(config != null){
if(config.ref){
ref = config.ref
}
if(config.key){
key = config.key
}
for(let propName in config){
if(!(['ref','key'].includes(propName))){
props[propName] = config[propName]
}
}
}
我们只需要把config的key和ref分别给到我们返回对象里面的key和ref。再便利一下config,拿出来的属性和值把key和ref排除。最终我们的config属性就处理好了。
最后一步就是处理children属性了。而React.createElement方法的第三个参数,也可以是第四个参数(就是后面的所有参数)。都可以为字符串,或者是React元素。
这里的React元素我们不管它是通过JSX创建的,还是通过React.createElement方法创建的都可以
而参数的情况分两种:
第一种是只有三个参数,也就是children为一个值。这个时候props里面的children就是该字符串。
第二种是参数大于三个,这个时候,props里面的children是一个数组,数组里的元素就是后面的所有参数。
OK,有了上面的基础,就可以对children进行处理了:
let childrenLength = arguments.length - 2
if(childrenLength === 1){
props.children = children
}else if(childrenLength > 1){
let children = new Array(childrenLength)
for(let i = 0;i<childrenLength;i++){
children[i] = arguments[i+2]
}
props.children = children
}
这里通过arguments来判断参数的个数,进而确定分支条件。
然后再根据情况,确定props里的children。
最后再贴上完整的createElement方法(简易版):
function createElement(type, config, children){
let props = {}
let myType = ''
let key = ''
let ref = ''
if(isValidElementType(type)){
myType = type;
}
if(config != null){
if(config.ref){
ref = config.ref
}
if(config.key){
key = config.key
}
for(let propName in config){
if(!(['ref','key'].includes(propName))){
props[propName] = config[propName]
}
}
}
let childrenLength = arguments.length - 2
if(childrenLength === 1){
props.children = children
}else if(childrenLength > 1){
let children = new Array(childrenLength)
for(let i = 0;i<childrenLength;i++){
children[i] = arguments[i+2]
}
props.children = children
}
return {
$$typeOf: 'react.element',
type: myType,
props,
key,
ref
}
}
OK,上面只是实现了一个比较简单的React.createElement方法,但是懂了其中的过程,我们就可以看一下真正的React.createElement源码:
isValidElementType方法
function isValidElementType(type) {
if (typeof type === 'string' || typeof type === 'function') {
return true;
} // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill).
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing ) {
return true;
}
if (typeof type === 'object' && type !== null) {
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== undefined) {
return true;
}
}
return false;
}
这里面也主要就是判断type的类型,不过判断情况多了几种React自带的元素类型。
createElement方法
function createElement(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 (hasValidRef(config)) {
ref = config.ref;
{
warnIfStringRefCannotBeAutoConverted(config);
}
}
if (hasValidKey(config)) {
{
checkKeyStringCoercion(config.key);
}
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];
}
}
} // 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];
}
{
if (Object.freeze) {
Object.freeze(childArray);
}
}
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 (key || ref) {
var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};
这个方法主要是对config和children进行处理。
其余的部分就不粘过来了,对源码感兴趣的可以自己打断点尝试一哈!