ReactElement

一、JSX到ReactElement的转化

利用babel插件将编写的JSX代码转化为React.createElement代码:

function Cmp(){
    return <a>123</a>
}

<Cmp id="app">
  <span>1</span>
  <span>2</span>
</Cmp>

编译后得React代码:

function Cmp() {
    return React.createElement("a", null, "123");
  }

const jsx = React.createElement(
    Cmp,
    { id: "app" },
    React.createElement("span", null, "1"),
    React.createElement("span", null, "2")
);

加上render的示例:

// 转换前
render() {
    return (
        <div className="container">
            <h1 className="title">React learning</h1>
            <List data={this.state.data} />
        </div>
    );
}

// 转换后
render() {
    return React.createElement("div", {
        className: "container"
    },
    React.createElement("h1", { className: "title"}, "React learning"),
    React.createElement(List, { data: this.state.data }));
}

二、ReactElement源码解读

1、React源码入口

packages\react\src\React.js

2、ReactElement源码入口

packages\react\src\ReactElement.js

3、createElement核心代码

/**
 * @param type  表示当前节点的类型,可以是原生的DOM标签字符串,
 * 也可以是函数定义组件或者其它类型,如果是组件第一个字母大写区分
 * @param config   表示当前节点的属性配置信息
 * @param  children  表示当前节点的子节点,可以不传,也可以传入原始的字符串文本,甚至可以传入多个子节点
 * @returns 返回的是一个ReactElement对象
 */
export function createElement(type, config, children) {
    // 声明一堆变量
    let propName;
    // 用于存放config中的属性,但是过滤了一些内部受保护的属性名
    const props = {};
    // 将config中的key和ref属性使用变量进行单独保存
    let key = null;
    let ref = null;
    let self = null;
    let source = null;

    // config为null表示节点没有设置任何相关属性
    if (config != null) {
        // 验证ref有效性  config.ref !== undefined
        if (hasValidRef(config)) {
            ref = config.ref;
        }
        // 验证key有效性  config.key !== undefined
        if (hasValidKey(config)) {
            key = "" + config.key;
        }

        self = config.__self === undefined ? null : config.__self;
        source = config.__source === undefined ? null : config.__source;

        // 用于将config中的所有属性在过滤掉内部受保护的属性名后,将剩余的属性全部拷贝到props对象中存储
     // const RESERVED_PROPS = {
    //   key: true,
    //   ref: true,
    //   __self: true,
    //   __source: true,
    // };
        for (propName in config) {
            if (
                hasOwnProperty.call(config, propName) &&
                !RESERVED_PROPS.hasOwnProperty(propName)
            ) {
                props[propName] = config[propName];
            }
        }
    }

    // 由于子节点的数量不限,因此从第三个参数开始,判断剩余参数的长度,具有多个子节点则props.children属性存储为一个数组
    const childrenLength = arguments.length - 2; // 减2是排除type, config
    if (childrenLength === 1) {
        // 单节点的情况下props.children属性直接存储对应的节点
        props.children = children;
    } else if (childrenLength > 1) {
         // 多节点的情况下则根据子节点数量创建一个数组
        const childArray = Array(childrenLength);
        for (let i = 0; i < childrenLength; i++) {
            childArray[i] = arguments[i + 2];
        }
        props.children = childArray;
    }

     // 此处用于解析静态属性defaultProps
  // 针对于类组件或函数定义组件的情况,可以单独设置静态属性defaultProps
  // 如果有设置defaultProps,则遍历每个属性并将其赋值到props对象中(前提是该属性在props对象中对应的值为undefined)
    if (type && type.defaultProps) {
        const defaultProps = type.defaultProps;
        for (propName in defaultProps) {
            if (props[propName] === undefined) {
                props[propName] = defaultProps[propName];
            }
        }
    }

    // 返回ReactElement对象
    return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current,
        props
    );
}

1、核心逻辑:调用一个方法返回一个element的核心对象,包含各种属性、key等。
2、在类组件的render方法中最终返回的是由多个ReactElement对象组成的多层嵌套结构,所有的子节点信息均存放在父节点的props.children属性中

/**
 * 为一个工厂函数,每次执行都会创建并返回一个ReactElement对象
 * @param type 表示节点所对应的类型,与React.createElement方法的第一个参数保持一致
 * @param key 表示节点所对应的唯一标识,一般在列表渲染中我们需要为每个节点设置key属性
 * @param ref 表示对节点的引用,可以通过React.createRef()或者useRef()来创建引用
 * @param self 该属性只有在开发环境才存在
 * @param source 该属性只有在开发环境才存在
 * @param owner 一个内部属性,指向ReactCurrentOwner.current,表示一个Fiber节点
 * @param props 表示该节点的属性信息,在React.createElement中通过config,children参数和defaultProps静态属性得到
 * @returns 返回一个ReactElement对象
 */
const ReactElement = function(type, key, ref, self, source, owner, props) {
    const element = {
        // 每个对象的唯一标识-symbol常量
        $$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
    };
    return element;
};

1、ReactElement对象主要增加了一个$$typeof属性用于标识该对象是一个React Element类型。
2、REACT_ELEMENT_TYPE在支持Symbol类型的环境中为symbol类型,否则为number类型的数值。
3、与REACT_ELEMENT_TYPE对应的还有很多其他的类型,均存放在shared/ReactSymbols目录中