美文网首页react源码分析
react源码阅读笔记(1) 创建一个react组件

react源码阅读笔记(1) 创建一个react组件

作者: Kaku_fe | 来源:发表于2017-09-26 19:52 被阅读72次

本文使用的是react 15.6.1的代码

从创建一个组件开始

在我们使用React组件的时候,我们都会写类似这样一段的代码

class App extends React.Component {
    constructor(props) {
        super(props)
    }

    render() {
        let {text} = this.props;
        return <div id="app">
            <span>{text}</span>
        </div>
    }
}
    

这是一段简单的用ES6+JSX写的代码,抛开这一些语法糖,我们看看经过babel编译后是什么样子的

_createClass(App, [{
        key: "render",
        value: function render() {
            var a = this.props.a;

            return _react2.default.createElement(
                "div",
                { id: "app" },
                _react2.default.createElement(
                    "span",
                    null,
                    a
                )
            );
        }
    }]);

因为笔者用的是ES6的代码,所以经过babel编译,看着和react差别比较大,不过对观察源码并无太多影响

首先我们看到,编译后他调用了createClass方法,是不是和用ES5写react很像,ES5写组件是通过React.createClass来完成,通过编译后我们可以知道,使用 extends React.Component写法,其实内部也是在使用createClass来生成一个React组件,现在我们来看看React.createClass的源代码(先去掉process.env.NODE_ENV !== 'production'里面的代码,该部分只用于调试,对阅读无影响)

代码地址:createClass.js

var {Component} = require('ReactBaseClasses');
var {isValidElement} = require('ReactElement');
var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
var factory = require('create-react-class/factory');

module.exports = factory(Component, isValidElement, ReactNoopUpdateQueue);

通过代码了解createClass会先执行factory方法,并将结果返回出去,

代码地址:factory.js

//仅仅保留核心实现
function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue){
  function createClass(spec) {
   
   //构造函数
    var Constructor = identity(function(props, context, updater) {


      if (this.__reactAutoBindPairs.length) {
        bindAutoBindMethods(this);
      }

      this.props = props;
      this.context = context;
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;

      this.state = null;

      //如果实例存在getInitialState方法,将会调用他获取state
      var initialState = this.getInitialState ? this.getInitialState() : null;
      this.state = initialState;
    });
    // 继承ReactClassComponent
    Constructor.prototype = new ReactClassComponent();
    Constructor.prototype.constructor = Constructor;
    Constructor.prototype.__reactAutoBindPairs = [];


    //如果class中定义了getDefaultProps方法。调用getDefaultProps获取默认的props
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }

    // 对外暴露的方法,如render,shouldComponentUpdate等,如果在组件中没有实现,则返回null
    // 暴露出来的所有方法可以在factory中变量ReactClassInterface看到
    for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }

    return Constructor;
  }

  return createClass;
 }

源码中,我们发现其构造函数为identity(),那么identity做了什么呢? 这里面代码非常简单

// 仅仅是创建一个匿名函数
function identity(fn) {
  return fn;
}

通过源码,我们了解到react.createClass具有以下功能

  1. createClass方法执行后会返回一个function Constructor,当组件类真正实例化的时候(new等)会初始化pro同时调用initialState方法获取state,同时,该组件类一旦被声明(非调用),会优化调用getDefaultProps给defaultProps赋值,该defaultProps不属于任何实例独有,所有实例会共用getDefaultProps,类似于Java中的静态变量,当然你也可以直接 App.defaultProps = {text:'hello react'}
  2. 如本文开头示例代码,定义了render函数,react暴露出来的方法如果未定义,会自动变成null。

class App extends React.Component源码了解了,我们在看看我们自定义组件中重写的render方法,在jsx中,render方法返回的是一个html+js的混合结构,由babel转义可以知道,实际着一些混合结构会先使用React.createElement方法

代码地址:ReactElement.js

ReactElement.createElement = function(type, config, children) {
  // type对应的就是jsx中的标签,可能是div这样的html元素,也可能是一个reactElement
  // config 对应的是标签上的属性
  // 对应孩子节点,可能会有多个
  var propName;

  // Reserved names are extracted
  var props = {};

  var key = null;
  var ref = null;
  var self = null;
  var source = null;

  if (config != null) {
    //如果config存在ref
    if (hasValidRef(config)) {
      //设置ref值等于config中的ref
      ref = config.ref;
    }
    //同理设置key
    if (hasValidKey(config)) {
      //转为字符串
      key = '' + config.key;
    }
    //初始化self和source
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    //将config中的除了key ref __self __source放入props中
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  //设置props的children属性,children可能是一个对象/数组
  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];
    }
    props.children = childArray;
  }

  /**
   *  设置props,在这里如果type是ReactElement的话,则将defaultProps中属性填入props中,由上面代码知道
   *  config对应着标签中的属性,属性上的值也会将除了key ref __self __source的属性放入props中,如果有某个属性同时存在与config以及
   *  defaultProps中,那么会用config中的属性(也就是标签上的属性)
   */
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  //这时候返回ReactElement对象
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current, //null
    props,
  );

执行该方法,会返回执行 ReactElement函数
代码地址:ReactElement.js

var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // 标明该元素是一个ReactElement
    $$typeof: REACT_ELEMENT_TYPE,

    type: type,
    key: key,
    ref: ref,
    props: props,

    _owner: owner, //createElement调用时,为null
  };


  return element;
};

不难看出,返回了一个对象element,该对象也就是react中描述的virtual dom,需要注意,createElement的第一个参数,在react中,如果render的是原生dom标签(div,span),那么type的值就是 字符串div/span,如果是一个自定义react组件,那么值就是一个ReactClass,即React.createClass的返回值

相关文章

网友评论

本文标题:react源码阅读笔记(1) 创建一个react组件

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