React源码解析之React.createElement()和ReactElement()

2019-07-14 admin

一、JSX语法转换到Js语法 从 JSX 转换到 JS 会用到React.createElement(),所以先熟悉下 JSX 到 JS 的转换。

这边是 JSX 语法:

<div id='one' class='two'>
    <span id="spanOne">this is spanOne</span>
    <span id="spanTwo">this is spanTwo</span>
</div>

这边是转化成的 js 语法:

React.createElement(
  "div",
 { id: "one", class: "two" },
 React.createElement( "span", { id: "spanOne" }, "this is spanOne"), 
 React.createElement("span", { id: "spanTwo" }, "this is spanTwo")
);

React.createElement("标签名","Object,包含div的props",'children子节点1','children子节点2','...')


这边是 JSX 语法:

function Div(){ }

<Div id='one' class='two'>
    <span id="spanOne">this is spanOne</span>
    <span id="spanTwo">this is spanTwo</span>
</Div>

这边是转化成的 js 语法:

React.createElement(Div, {} , xxx );

如果标签名大写,则表示组件 Div(也就是function),小写表示 html 的标签 <div>

也就是说:自定义的组件必须大写字母开头


二、React.createElement() 源码地址:https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactElement.js

作用: 创建React.Element,示例请看一、JSX语法转换到Js语法

源码:

//注意:react只写了3个参数,实际上,从第三个参数往后都是children
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;
  //赋给标签的props不为空时
  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      //防止是Number
      key = '' + config.key;
    }
    //__self、__source 暂时不知道是干啥用的属性
    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) {
      //如果config中的属性不是标签原生属性,则放入props对象中
      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);
    //依次将children push进array中
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    //如果是development环境的话
    if (__DEV__) {
      //冻结array
      //未在微信发表
      //https://www.jianshu.com/p/91e5dc520c0d?utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=weixin-friends&from=singlemessage&isappinstalled=0
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    //开发中写的this.props.children就是子元素的集合
    props.children = childArray;
  }

  // Resolve default props

  //为传入的props设置默认值,比如:
  //class Comp extends React.Component{
  //  static defaultProps = {
  //     aaa: 'one',
  //     bbb: () => {},
  //     ccc: {},
  //   };
  //
  // }

  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      //如果props数组中未设值,则设置默认值(注意:null也算设置了值)
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

  if (__DEV__) {
    //一旦ref或key存在
    if (key || ref) {
      //如果type是组件的话,赋值displayName
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      //可不看
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  return ReactElement(
    type,  //'div'
    key,  //null
    ref,  //null
    self, //null
    source, //null
    ReactCurrentOwner.current, //null或Fiber
    props, //自定义的属性、方法,注意:props.children=childArray
  );
}

解析:

(1)hasValidRef()

作用: 判断是否设置了ref的属性,true有,false没有

源码:

//判断是否设置了ref的属性,true有,false没有
function hasValidRef(config) {
  //如果是development环境的话
  if (__DEV__) {
    //如果config中存在ref属性的话
    //在jQuery中 .call/.apply的更大作用是绑定this
    if (hasOwnProperty.call(config, 'ref')) {
      //Object.getOwnPropertyDescriptor() es5
      //Object.getOwnPropertyDescriptors() es6
      //https://blog.csdn.net/qq_30100043/article/details/53424963

      //返回对象config的属性ref 的get对象
      const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
      //如果isReactWarning,则忽略ref属性,返回false
      if (getter && getter.isReactWarning) {
        return false;
      }
    }
  }
  //<div ref={this.optionsTEchart} ></div>
  return config.ref !== undefined;
}

注意:__DEV__表示测试环境,是供React内部测试的,可以不看,我简单地解释了下

② 在jQueryfn.call(xxx,a1,a2,...)fn.apply(xxx,array)的更大作用是绑定this

Object.getOwnPropertyDescriptor()的作用是返回某个对象属性的描述对象( descriptor )

比如:

var obj = { p: 'a' };
Object.getOwnPropertyDescriptor(obj, 'p')
//返回
// Object { value: "a",
// writable: true,
// enumerable: true,
// configurable: true
}

关于Object.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptors()的区别,请看:https://blog.csdn.net/qq_30100043/article/details/53424963

(2)hasValidKey

作用: 判断是否设置了key,同hasValidRef,不解释了

源码:

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

(3)虽然React.createElement()只传三个参数,但从第三个参数开始,利用arguments来获取剩下的参数

(4)Object.freeze() 使用Object.freeze()冻结的对象是最严格的防篡改级别,既不可扩展,也是密封的,不可修改属性。

对于 JS 库作者而言,冻结对象可防止有人修改库的核心对象。

关于 JS 冻结对象的方法,请看:JS红皮书解读之防篡改对象

(5)最后是 return 了ReactElement()方法,注意props中的children属性就是React组件的children

react组件的children属性不会被覆盖: 父组件:

return(
  <DashBoard children={'bbbb'}>
    aaaa
  </DashBoard>
)

子组件:

console.log(this.props)

结果:

三、ReactElement() 源码地址:https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactElement.js

作用: 通过工厂模式创建React.Element对象,你打印一个React组件的话,会是下面这个样子:

源码:

/**
 * Factory method to create a new React element. This no longer adheres to
 * the class pattern, so do not use new to call it. Also, no instanceof check
 * will work. Instead test $typeof field against Symbol.for('react.element') to check
 * if something is a React Element.
 *
 * @param {*} type
 * @param {*} props
 * @param {*} key
 * @param {string|object} ref
 * @param {*} owner
 * @param {*} self A *temporary* helper to detect places where `this` is
 * different from the `owner` when React.createElement is called, so that we
 * can warn. We want to get rid of owner and replace string `ref`s with arrow
 * functions, and as long as `this` and owner are the same, there will be no
 * change in behavior.
 * @param {*} source An annotation object (added by a transpiler or otherwise)
 * indicating filename, line number, and/or other information.
 * @internal
 */

// type,  //'div'
// key,  //null
// ref,  //null
// self, //null
// source, //null
// ReactCurrentOwner.current, //null或Fiber
// props, //自定义的属性、方法,注意:props.children=childArray
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    //标识element的类型
    //因为jsx都是通过createElement创建的,所以ReactElement的类型固定:为REACT_ELEMENT_TYPE
    //重要!因为react最终渲染到DOM上时,需要判断$typeof===REACT_ELEMENT_TYPE
    $typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    //设置元素的内置属性
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    //记录创建react.element的组件(this?)
    _owner: owner,
  };

  if (__DEV__) {
    // The validation flag is currently mutative. We put it on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.

    //验证flag是不固定的.我们将其放置在一个store上,从而能冻结整个object
    //这样一旦它们被用在开发环境时,用WeakMap代替

    //WeakMap
    // http://es6.ruanyifeng.com/#docs/set-map
    element._store = {};

    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    //方便测试用
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
    // self and source are DEV only properties.
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });
    // Two elements created in two different places should be considered
    // equal for testing purposes and therefore we hide it from enumeration.
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

解析: (1)通过$typeof确保是React.Element类型,从而渲染到真正的DOM树上

(2)__DEV__注释中有提到WeakMap简单说下WeakMap的作用: 你往WeakMap上的对象 a 添加数据,对象 b 引用 对象 a,之后对象 b 不引用 对象 a,a 就被垃圾回收,不用WeakMap的话,即使对象 b 以后不引用对象 a了,a 也不会被垃圾回收,因为强引用是不会触发垃圾回收机制的,需要手动删除,很麻烦。

想更详细地了解的话,可以参考下这篇文章: http://es6.ruanyifeng.com/#docs/set-map

关于垃圾回收机制,请看:浅谈下垃圾回收机制(1)

(3)该方法比较简单,就是初始化了一个对象,并将其标记为React.Element对象($typeof=REACT_ELEMENT_TYPE


(完)

[转载]原文链接:https://segmentfault.com/a/1190000019757201

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-69753.html

文章标题:React源码解析之React.createElement()和ReactElement()

相关文章
Facebook发布React Native!
React.js Conf 2015会议上,Facebook发布了React Native。 React.js 是 Facebook 推出的一个用来构建用户界面的 JavaScript 库。 React中,把一切东西都看成组件,而且所有组件...
2015-11-12
如何为高负载网络优化Nginx 和 Node.js?
译者:AlfredCheung 在搭建高吞吐量web应用这个议题上,NginX和Node.js可谓是天生一对。他们都是基于事件驱动模型而设计,可以轻易突破Apache等传统web服务器的C10K瓶颈。预设的配置已经可以获得很高的并发,不过,...
2015-11-12
梳理前端开发使用eslint-prettier检查和格式化代码
问题痛点 在团队的项目开发过程中,代码维护所占的时间比重往往大于新功能的开发。因此编写符合团队编码规范的代码是至关重要的,这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性。 对于代码版本管理系统(svn 和 git或者其他)...
2018-05-07
jQuery中DOM树操作之使用反向插入方法实例分析
本文实例讲述了jQuery中DOM树操作之使用反向插入方法。分享给大家供大家参考。具体分析如下: 使用反向插入方法 这里我们先把创建的内容插人到元素前面,然后再把同一个元素插人到文档 中的另一个位置。通常,当在jQuery中操作元素时,利用...
2015-11-13
React Native 用JavaScript编写原生ios应用
ReactNative 可以基于目前大热的开源JavaScript库React.js来开发iOS和Android原生App。而且React Native已经用于生产环境——Facebook Groups iOS 应用就是基于它开发的。 Re...
2015-11-12
JavaScript游戏之连连看源码分享
JavaScript游戏之连连看源码 下载地址:JavaScript游戏之连连看源码 解压密码:www.javascriptcn.com ...
2015-11-12
jQuery中DOM树操作之复制元素的方法
本文实例讲述了jQuery中DOM树操作之复制元素的方法。分享给大家供大家参考。具体分析如下: 复制元素 前面提到的操作包括:插人新创建的元素、将元素从文档中的一个位置移动 到另一个位置,以及通过新元素来包装已有的元素。可是,有时候也会用到...
2015-11-13
Node.js的不足之处
跨平台编程能力不够强大 Bowery团队指出Go能很方便地在不同系统里进行程序编译,这是他们转入Go的重要原因之一。 作为开发平台,对Linux,Windows,OSX等常见操作系统提供支援是能否吸引开发者的基本要素。在Go中,开发者可以针...
2015-11-12
2015 Web 2.0和AJAX如何做好优化
2015如何让做好web2.0和ajax的优化?JavaScript中文网总结当下提出以下四大优化意见,旨在帮助W前端开发人员有效利用APM解决上述问题。 随着Web应用程序速度与效率快速增长,网站已经成为企业与其客户进行交互的第一途径——...
2015-11-12
[翻译]基于Webpack4使用懒加载分离打包React代码
原文地址:https://engineering.innovid.com/code-splitting-using-lazy-loading-with-react-redux-typescript-and-webpack-4-3ec601...
2018-03-11
回到顶部