React源码学习——ReactElement.createElement

最近在学习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;
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,804评论 0 24
  • 学习如何在Flow中使用React 将Flow类型添加到React组件后,Flow将静态地确保你按照组件被设计的方...
    vincent_z阅读 6,323评论 4 21
  • 以下内容是我在学习和研究React时,对React的特性、重点和注意事项的提取、精练和总结,可以做为React特性...
    科研者阅读 8,217评论 2 21
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 8,046评论 2 35
  • 纪念每次无论何种理由的离开,夜未褪去,黎明初醒,在路上 放飞白色的鸽子于天际我所有的想象和思绪变成它们白色的羽翼 ...
    郭安安阅读 331评论 10 17