• React.createElement方法源码解析(原理详解)


    摘要

    在上一篇说过,React创建元素有两种方式:
    第一种是通过JSX方式创建,第二种是通过React.createElement方法创建。但是通过JSX创建React元素的方式,最终也会经过babel进行转换,再用React.createElement进行元素创建

    而这一篇文章,主要是讲一下React.createElement是如何创建React元素的。

    1.创建方法

    为了了解React.createElement这个方法,我们自己手动去实现一个简易版的。

    OK,首先我们通过React.createElement方法创建一个React元素,并且打印出来:

        const b = React.createElement('div',{data: '1234'},'oneTwo')
        console.log(b);
    
    • 1
    • 2

    打印出来的结果为:
    在这里插入图片描述
    所以,如果我们要实现出来React.createElement这个方法,定义的时候我们可以这么写:

    function createElement(type, config, children){
    
      let props = {}
      let myType = ''
      let key = ''
      let ref = ''
    
      return {
        $$typeOf: 'react.element',
        type: myType,
        props,
        key,
        ref
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这里我们不管**$$typeOf**这个变量,只考虑其他的变量,最终返回的对象应该是这个数据结构。

    2.处理type

    好了,方法的定义已经写完了。针对于传进来的这三个参数,我们一个一个看。

    第一个参数type,它代表的是创建React元素的类型。
    这里面可以是传统的HTML元素,例如div,h1,span等标签。
    也可以是一个React组件(注意这里哦)。

    而React创建组件又有两种方式,所以type的值,有三种数据类型:

    (1)字符串:例如"div",“h1”,“span”,"p"等标签
    (2)函数:通过函数的方式创建React组件
    (3)类:通过class的方式创建React组件

    而这个时候就有一个问题!

    class Demo {
    
    }
    
    typeof Demo
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面的值应该为什么呢?答案是function,所以在这里我们只需要考虑type为string和function两种类型即可。

    所以我们可以写一个判断类型的方法:

    function isValidElementType(type){
      if(typeof type === 'string' || typeof type === 'function'){
        return true;
      }
      
      return false;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在我们的主方法里面引用:

    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
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.处理config

    对于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]
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    我们只需要把config的key和ref分别给到我们返回对象里面的key和ref。再便利一下config,拿出来的属性和值把key和ref排除。最终我们的config属性就处理好了。

    4.处理children

    最后一步就是处理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
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里通过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
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    5.对比真正的React.createElement源码

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这里面也主要就是判断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);
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    这个方法主要是对config和children进行处理。

    其余的部分就不粘过来了,对源码感兴趣的可以自己打断点尝试一哈!

  • 相关阅读:
    RK3399驱动开发 | 12 - AP6255 SDIO WiFi 调试(基于linux4.4.194内核)
    学测试要天赋吗?从功能测试到测试开发,收入差距可不止是一套房~
    基于单片机的智能交通控制系统研究
    Debezium系列之:记录源库表增加字段,debezium采集的数据丢失新增字段的原因和相对应的解决方法
    【树莓派4B】如何点亮树莓派的LED灯
    实践练习命令执行
    项目上线后,老板喜提法拉利
    04.在谷歌浏览器中安装模拟浏览器ChromeDriver的详细步骤
    /usr/bin/ld: cannot find -lc错误原因及解决方法
    使用ESP8266构建家庭自动化系统
  • 原文地址:https://blog.csdn.net/weixin_46726346/article/details/126471596