React中createElement和ReactElement

2019年12月21日Web前端

库或框架用多了,如果只是熟记一些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被设置成了REACTELEMENTTYPE,当react元素渲染到DOM上时,也需要判断$$typeof===REACTELEMENTTYPE。

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对象。