博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入浅出React核心源码解析(2) createElement与ReactElement
阅读量:6716 次
发布时间:2019-06-25

本文共 5697 字,大约阅读时间需要 18 分钟。

一、createElement

上一章我们讲到了所有jsx语法都会被转成createElement。

那么createElement的实现是怎样的呢?

首先我们从github克隆下来react的源码库,我们先来分析下react源码库的文件布局。

react工程根目录下有packages文件夹,其间放置的是react的各个包,我们暂时把着力点放于react目录下。内部是react源码实现。

抛出去一些非必要的检测,和warn代码,核心的react代码其实只有几百行。react源码本身并不复杂,负责渲染的react-dom才是最复杂的。

react目录的src,就是react的核心实现了。

createElement方法位于ReactElement.js文件内,实现如下:

export function createElement(type, config, children) {  let propName;  // Reserved names are extracted  const props = {};  let key = null;  let ref = null;  let self = null;  let source = null;  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];      }    }  }  // Children can be more than one argument, and those are transferred onto  // the newly allocated props object.  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;  }  // Resolve default props  if (type && type.defaultProps) {    const defaultProps = type.defaultProps;    for (propName in defaultProps) {      if (props[propName] === undefined) {        props[propName] = defaultProps[propName];      }    }  }  if (__DEV__) {    if (key || ref) {      const 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,  );}复制代码

这里面有一些开发环境下检测,和外部调用方法,可能会使阅读者精力分散,我们来稍微改动精简下代码,使功能一致,同时更好阅读:

export function createElement(type, config, ...children) {  const {ref = null, key = null} = config || {};  const {current} = ReactCurrentOwner;  const {defaultProps} = type || {};  const props = assignProps(config, defaultProps, children);  return new ReactElement({    type,    key: '' + key,    ref,    current,    props,  });}复制代码

经过精简和简化后,createElement仅有30行代码。我们来逐行解析下。

/** *  * @param type {string | function | object}   *        如果type是字符串,那就是原生dom元素,比如div *        如果是function或者是Component的子类 则是React组件 *        object 会是一些特殊的type 比如fragment * @param config {object} *        props 和key 还有ref 其实都是在config里了 * @param children *        就是由其他嵌套createElement方法返回的ReactElement实例 * @returns {ReactElement} *  */export function createElement(type, config, ...children) {      // 给config设置一个空对象的默认值  // ref和key 默认为null  const {ref = null, key = null} = config || {};  // ReactCurrentOwner负责管理当前渲染的组件和节点  const {current} = ReactCurrentOwner;  // 如果是函数组件和类组件 是可以有defaultProps的  // 比如  // function A({age}) {return 
{age}
} // A.defaultProps = { age:123 } const {defaultProps} = type || {}; // 把defaultProps和props 合并一下 const props = assignProps(config, defaultProps, children); // 返回了一个ReactElement实例 return new ReactElement({ type, key: '' + key, ref, current, props, });}复制代码

ref和key不用多说,大家都知道是干啥的。之前有个同事问过我,key明明传的是数字,为啥最后成了字符串,症结就在上面的ReactELement构造函数传参的key那里了,key:''+key

assignProps是我抽象了一个方法,合并defaultProps和传入props的方法,稍后提供代码,其实在cloneElement方法里,也有一段类似代码,但是react并没有抽象出来,相对来说,会有代码冗余,暂且提炼出来。

重点在new ReactElement()。

react的代码里,ReactElement是个工厂函数,返回一个对象。但是我个人觉得比较奇怪。

第一、工厂函数生成实例,这个工厂函数不该大写开头。

第二、使用构造函数或者类来声明ReactElement难道不是一个更好,更符合语义的选择?

在这里,为了便于理解,把ReactElement从工厂函数,改变成了一个类,createElement返回的就是一个ReactElement类的实例。

下面看下asssignProps的实现,该方法在cloneElement也可以复用:

const RESERVED_PROPS = ['key', 'ref', '__self', '__source'];export function assignProps(config, defaultProps, children) {      const props = {        children,    };    config = config || {};    for (const propName in config) {        if (            config.hasOwnProperty(propName) &&            !RESERVED_PROPS.includes(propName)        ) {            props[propName] = config[propName];            if (                props[propName] === undefined &&                defaultProps &&                defaultProps[propName] !== undefined            ) {                props[propName] = defaultProps[propName];            }        }    }    return props;}复制代码

二、ReactElement

create返回的是个ReactElement实例,那么ReactElement又是啥呢?

抛出去dev时的代码,精简后如下:

const ReactElement = function(type, key, ref, self, source, owner, props) {  const element = {    $$typeof: REACT_ELEMENT_TYPE,    type: type,    key: key,    ref: ref,    props: props,    _owner: owner,  };  return element;};复制代码

可以看到,其实就是返回了一个对象,我们现在可以简单而浮夸的想象下,react的render机制其实就是读取这些数据结构,然后根据结构树,层层根据原生dom方法渲染而成。(暂时这样想象)

经过用类改造后的代码为:

export class ReactElement {  constructor(elementParams) {    const {type, key, ref, current, props} = elementParams || {};    // 如果是原生标签比如h1 那就是字符串    // 如果是组件 则是组件的引用    this.type = type;    // key    this.key = key;    // ref    this.ref = ref;    // 延后再讲    this._owner = current;    // props    this.props = props;    // 类型标识 新版本中的React里是symbo    this.$$typeof = REACT_ELEMENT_TYPE;  }}复制代码

三、总结

本章的重点在于,在react中,jsx标签的本质就是ReactElement,createElement会对组件或者dom的type和props经过一层封装处理,最后返回了ReactElement的实例。

转载地址:http://eorlo.baihongyu.com/

你可能感兴趣的文章
Font Awesome
查看>>
Dubbo消费者
查看>>
虚拟化中虚拟机处理器核数与物理主机cpu的关系
查看>>
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type
查看>>
MYSQL: mysqlbinlog读取二进制文件报错read_log_event()
查看>>
随机产生由特殊字符,大小写字母以及数字组成的字符串,且每种字符都至少出现一次...
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
java21:捕鱼达人
查看>>
Zabbix 服务端搭建
查看>>
Java - 一个单例
查看>>
学习JAVA 持续更新
查看>>
Spring propertyConfigurer类
查看>>
Linux系统分析工具之uptime,top(一)
查看>>
EIGRP之DUAL(扩散更新算法)
查看>>
cacti自定义数据收集脚本,创建Data Templates和Graph Templates
查看>>
对你同样重要的非技术贴,一封有效的求职信的具体写法
查看>>
在路由器里插入和删除ACL
查看>>
我的友情链接
查看>>
OpenStack从入门到放弃
查看>>