美文网首页React.jsReact-Native 开发阵营React Native开发
React源码学习——ReactElement.createEl

React源码学习——ReactElement.createEl

作者: Sue1024 | 来源:发表于2018-01-25 23:46 被阅读157次

    最近在学习React的源码,从比较简单的创建ReactElement开始学起,以下是今天要啃的源码,可能有些地方还不是很深入,相信随着了解的增多,对react的理解也会更加深入:

    ReactElement.createElement = function (type, config, children) {
      var propName;
      var props = {};
      var key = null;
      var ref = null;
      var self = null;
      var 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;
        for (propName in config) {
          if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
            props[propName] = config[propName];
          }
        }
      }
      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 ("development" !== 'production') {
          if (Object.freeze) {
            Object.freeze(childArray);
          }
        }
        props.children = childArray;
      }
      if (type && type.defaultProps) {
        var defaultProps = type.defaultProps;
        for (propName in defaultProps) {
          if (props[propName] === undefined) {
            props[propName] = defaultProps[propName];
          }
        }
      }
      if ("development" !== 'production') {
        if (key || ref) {
          if (typeof props.$$typeof === 'undefined' || props.$$typeof !== REACT_ELEMENT_TYPE) {
            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);
    };
    

    How to trigger it ?

    两种方式可以触发上面这个方法:

    1. 使用JSX创建元素
    render() {
      return (
        <button className="button" type="button")}>Click</button>
      )
    }
    
    1. 使用React.createElement创建元素
    render() {
      var config = {
        className: 'button',
        type: 'button'
      };
      return React.createElement('button', config, 'Click');
    }
    

    JSX本质上是ReactJS创建元素的语法糖,它们都在做同一件事情,就是生成一个简单的按钮,babel会帮我们将JSX转化成Javascript。

    Parameters passed into the method

    这个方法看起来是接收三个参数,不过了解Javascript函数机制的话,就知道其实不是的,不过我们先假装它接收三个参数:

    1. type: 这个参数声明要创建什么类型的DOM元素,分两种,一种是原生DOM,比如div span h1等等,传入一个string,比如'div' 'span' 'h1'等等,即可正确的表示要创建的DOM类型。另一种是自定义的Web Component,比如MyComponent,此时需要引入这个组件,然后将组件作为第一个参数传入。

    The first part of a JSX tag determines the type of the React element.
    Capitalized types indicate that the JSX tag is referring to a React component. These tags get compiled into a direct reference to the named variable, so if you use the JSX <Foo /> expression, Foo must be in scope.(出自ReactJS官方文档)

    1. config: 用来声明组件的属性列表。
    2. children: 用来在组件内创建子元素,例子中是Click,我们也可以创建更复杂的子元素,比如div等等。

    Then, what happened?

    1. 首先,在config不为空的情况下,校验是否有合法的ref属性以及是否是合法的键值对。
    function hasValidRef(config) {
      if ("development" !== 'production') {
        if (hasOwnProperty.call(config, 'ref')) {
          var getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
          if (getter && getter.isReactWarning) {
            return false;
          }
        }
      }
      return config.ref !== undefined;
    }
    

    本菜鸟一直搞不懂问什么要写"development" !== 'production'这样一句永远为true的表达式(React源码中经常出现),欢迎大家评论解答_
    首先判断ref是否是config自身的属性,而不是从原型链上继承来的属性,然后再判断此ref是否有效。

    function hasValidKey(config) {
      if ("development" !== 'production') {
        if (hasOwnProperty.call(config, 'key')) {
          var getter = Object.getOwnPropertyDescriptor(config, 'key').get;
          if (getter && getter.isReactWarning) {
            return false;
          }
        }
      }
      return config.key !== undefined;
    }
    

    key的判断跟ref很类似。

    1. 装载self和source
      我们的例子中config__self __source均为undefined,这两个变量还没有搞懂,欢迎大家留言。

    2. config中的自有而非从原型链继承得来、非保留属性(key ref __self __source)存储到局部变量props中。

    3. 前面提到过假装只接受三个参数,其实可以接受多于三个参数的,那么第四个、第五个参数要怎么处理呢?这个方法中,它们被当做子元素来处理
      首先判断子元素的长度是否为1,是的话直接将第三个参数放入props.children中,如果大于1, 那么构建子元素数组,将第三个、第四个、第五个...参数依次放入这个数组中,然后将这个数组赋值给props.children
      将参数指定的属性设置好之后,开始处理指定元素类型带有默认值的属性,如果被设置默认值的属性没有被指定新值,那么存储默认值到props中。

    4. 接下来的一段代码,在props存在keyref的情况下,并且props.$$typeof为空或者不等于Symbol(react.element)或者60103时(即不是ReactElement),为props.key props.ref添加get属性,get属性指向一个函数,这个函数可以显示初次的报警信息。这一步显然与第1步有联系,但仍然想不出在什么情形下会显示警告,大家知道的话可以告诉我~
      下面这段代码是REACT_ELEMENT_TYPE的赋值操作

    var REACT_ELEMENT_TYPE = typeof Symbol === 'function' && Symbol['for'] && Symbol['for']('react.element') || 0xeac7;
    

    Symbol是ES6新定义的一种基本数据类型:
    MDN web docs: Symbol

    感觉defineXXXPropWarningGetter就是为key ref属性添加访问控制,不知道对不对...(原谅React小白留下这么多坑...),以下是代码:

    function defineKeyPropWarningGetter(props, displayName) {
      var warnAboutAccessingKey = function () {
        if (!specialPropKeyWarningShown) {
          specialPropKeyWarningShown = true;
          "development" !== 'production' ? warning(false, '%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName) : void 0;
        }
      };
      warnAboutAccessingKey.isReactWarning = true;
      Object.defineProperty(props, 'key', {
        get: warnAboutAccessingKey,
        configurable: true
      });
    }
    
    
    function defineRefPropWarningGetter(props, displayName) {
      var warnAboutAccessingRef = function () {
        if (!specialPropRefWarningShown) {
          specialPropRefWarningShown = true;
          "development" !== 'production' ? warning(false, '%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName) : void 0;
        }
      };
      warnAboutAccessingRef.isReactWarning = true;
      Object.defineProperty(props, 'ref', {
        get: warnAboutAccessingRef,
        configurable: true
      });
    }
    
    1. 万事俱备,现在可以正儿八经的创建ReactElement啦(构建ReactElement数据结构)
      首先给这个element打上react的标签(REACT_ELEMENT_TYPE),然后将校验合格的变量填充到element对象中,其中owner是ReactCurrentOwner.current,就是管理它的component, 并且定义_store.validated _self _source(是否可配置,是否可编辑,是否可枚举,值)
    var ReactElement = function (type, key, ref, self, source, owner, props) {
      var element = {
        $$typeof: REACT_ELEMENT_TYPE,
        type: type,
        key: key,
        ref: ref,
        props: props,
        _owner: owner
      };
    
      if ("development" !== 'production') {
        element._store = {};
        if (canDefineProperty) {
          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
          });
        } else {
          element._store.validated = false;
          element._self = self;
          element._source = source;
        }
        if (Object.freeze) {
          Object.freeze(element.props);
          Object.freeze(element);
        }
      }
      return element;
    };
    

    相关文章

      网友评论

        本文标题:React源码学习——ReactElement.createEl

        本文链接:https://www.haomeiwen.com/subject/pdjbaxtx.html