React 源码解析:第一章 React 元素树

288 阅读2分钟

😆 Hi,大家好,我是张玥卿云,一个面向未来编程的程序员!准备将 React 源码解析写成一系列文章分享给大家,希望大家喜欢~

一、前言

一个最简单的 React 程序往往包含三个步骤:

  1. 创建 React 元素树:
const element = React.createElement("div", {}, '');
  1. 创建 FiberRoot 节点并挂载到 DOM 容器节点上:
const root = ReactDOM.createRoot(container);
  1. FiberRoot 渲染第一步创建的 React 元素树:
root.render(element);

今天我们详细讲解的是 React 元素树的创建。

二、React 元素树

React 元素树是开发者和 React 交互的桥梁,元素树描述了目标网站的全貌, React 会解析元素树,转化为Fiber 架构,生成交互式的网页。

React 元素树由 React 元素构成,React 元素是 $$typeof 属性为 REACT_ELEMENT_TYPE 的对象,它由 ReactElement 工厂函数创建。它还具有 type,key,ref,props 四个基本属性:

属性介绍
type元素的类型, 可以为 HTML 元素或 React 组件
key元素在其同级兄弟中的的身份标志(唯一键值)
ref对组件实例的引用
props组件或 HTML 元素的属性值

React 元素还具有 _owner, _store, _self, _source 三个内部属性,其中 _owner 属性记录了创建该元素的起因,而 _store, _self 和 _source 在开发模式下才会被创建。

下面是 ReactElement 工厂函数的源码:

/** 创建 React 元素的工厂函数 */
const ReactElement = function(type, key, ref, self, source, owner, props) {
    const element = {
        /** 标识该对象为 React 元素 */
        $$typeof: REACT_ELEMENT_TYPEtype: type,
        key: key,
        ref: ref,
        props: props,
        _owner: owner
    }

    /** ... 此处省略 DEV 模式下的若干行代码 ... */

    return element;
} 

三、React 元素的创建

React.createElement 和 React.cloneElement 是我们熟知的可以获取 React 元素的 Api:

/** 创建一个 React 元素 */
React.createElement(<MyElement />, {elementProp: 'element-prop'}, children);

/** 克隆一个 React 元素 */
React.cloneElement(element, {newProp: 'new-prop'});

下面我们分别看看这两个 Api 的源码。

3.1 React.createElement

React.createElement 是最常见的创建 React 元素的 Api,它的执行流程是:

  1. 解析形参 config 获取变量 ref、key、self、 source、 props 和 props.children。
  2. 根据 defaultProps 初始化变量 props。
  3. 利用 1 中变量和 ReactElement 工厂函数创建 React 元素。

源代码如下:

export function createElement(type, config, children) {
    let propName;

    const props = {};

    let key = null;
    let ref = null;
    let self = null;
    let source = null;

    if(config != null) {
        /** 初始化 ref  */
        if(hasValidRef(config)) {
            ref = config.ref;
        }

        /** 初始化 key */
        if(hasValidKey(config)) {
            key = '' + config.key;
        }
    }

    /** 初始化 self 和 source  */
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source; 

    /** 初始化其他 props */
    for (propName in config) {
        if (
            hasOwnProperty.call(config, propName) && 
            !RESERVED_PROPS.hasOwnProperty(propName)
        ) {
            props[propName] = config[propName];
        }
    }

    /** 初始化 children */
    const childrenLength = arguments.length - 2;
    if(childrenLength === 1) {
        props.children = children;
    } else if (childrenLength > 1) {
        const childrenArray = Array(childrenLength);
        for(let i = 0; i < childrenLength; i++) {
            childrenArray = Array(childrenLength);
        }
        props.children = childrenArray;
    }

    /** 初始化 defaultProps */
    if(type && type.defaultProps) {
        const defaultProps = type.defaultProps;
        for(propName in defaultProps) {
            if(props[Name] in defaultProps) {
                props[propName] = defaultProps[propName];
            }
        }
    }

    /** 创建 React 元素 */
    return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current,
        props
    )
}

3.2 React.cloneElement

React.cloneElement 和 React.createElement 比较相似,我们经常用它来扩展已存在的 React 元素的功能,这个 api 主要做了以下几件事:

  1. 从原 React 元素中获取 props、key、ref、_self、_source、_owner 属性。
  2. 解析新配置,获取新的 ref、key、defaultProps 等属性并覆盖原属性。
  3. 根据 defaultProps 和 props 设置默认值。
  4. 获取 props.children。
  5. 通过工厂函数 ReactElement 创建 React 元素并返回。

源代码如下:

export function cloneElement(element, config, children) {
    if(element === null || element === undefined) {
        throw new Error(`React.cloneElement(...): The argument must be a React element, but you passed ${element}`)
    }   

    let propName;

    // 获取待克隆对象的 props
    const props = assign({}, element.props);

    /** 获取待克隆对象的 key  */

    let key = element.key;
    /** 获取待克隆对象的 ref*/
    let ref = element.ref;

    /** 获取待克隆对象的 _self、 _source、_owner 属性 */
    const self = element._self;
    const source = element._source;
    let onwer = element._owner;

    /** 解析新的配置 */
    if(config != null)  {

        /** 获取新的  ref */
        if(hasValidRef(config)) {
            ref = config.ref;
            owner = ReactCurrentOwner.current;
        }

        /** 获取新的 key  */
        if(hasValidKey(config)) {
            key = '' + config.key;
        }

        /** 获取 defaultProps  */
        let defaultProps;
        if(element.type && element.type.defaultProps) {
            defaultProps = element.type.defaultProps;
        }

        /** 根据新的 defaultProps 和从被克隆元素获取的 props 初始化 props */
        for(propName in config) {
            if(
                hasOwnProperty.call(config, propName) && 
                !RESERVED_PROPS.hasOwnProperty(propName)
            ) {
                if(config[propName] === undefined && defaultProps !== undefined) {
                    props[propName] = defaultProps[propName];
                } else {
                    props[propName] = config[propName];
                }
            }
        }

        /** 获取 children */
        const childrenLength = arguments.length - 2;
        if(chidlrenLength === 1) {
            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;
        }

        /** 通过工厂函数创建 React 元素并返回 */
        return ReactElement(element.type, key, ref, self, source, owner, props);
    }
}

四、结尾

这些就是 ReactElement 元素树的创建过程,如有疑惑,欢迎大家一起讨论~ @V@ ~

😈 如感兴趣,可联系本人:张玥卿云

🐼 其他