阅读 46

React源码解析 createElement + Component + PureComponent

上一篇介绍了React的Api,今天介绍createElement和React的Component相关操作。看了很多其他人的博客,对于React16的fiber架构还是无法了解,尽管我知道他很牛逼。发现了解React的源码还是需要从渲染开始进行了解,明白从一个组件到挂载再到响应式的所有操作。

首先先写一个简单的React组件

import React from 'react';
import ReactDom from 'react-dom';

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: 'Hello World'
        }
    }
    componentDidMount() {
        console.log('component did mount');
    }
    render() {
        return (
            <div>
                { this.state.name }
            </div>
        )
    }
};

ReactDom.render(
    <App />,
    document.getElementById('root')
);
复制代码

使用babel编译这个jsx文件

"use strict";

var _react = _interopRequireDefault(require("react"));

var _reactDom = _interopRequireDefault(require("react-dom"));

function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
        "default": obj
    };
}

function _typeof(obj) {
    if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
        _typeof = function _typeof(obj) {
            return typeof obj;
        };
    } else {
        _typeof = function _typeof(obj) {
            return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
        };
    }
    return _typeof(obj);
}

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
    }
}

function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
}

function _possibleConstructorReturn(self, call) {
    if (call && (_typeof(call) === "object" || typeof call === "function")) {
        return call;
    }
    return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
    if (self === void 0) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return self;
}

function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
}

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function");
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            writable: true,
            configurable: true
        }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
        o.__proto__ = p;
        return o;
    };
    return _setPrototypeOf(o, p);
}

var App =
    /*#__PURE__*/
    function (_React$Component) {
        _inherits(App, _React$Component);

        function App(props) {
            var _this;

            _classCallCheck(this, App);

            _this = _possibleConstructorReturn(this, _getPrototypeOf(App).call(this, props));
            _this.state = {
                name: 'Hello World'
            };
            return _this;
        }

        _createClass(App, [{
            key: "componentDidMount",
            value: function componentDidMount() {
                console.log('component did mount');
            }
        }, {
            key: "render",
            value: function render() {
                return _react["default"].createElement("div", null, this.state.name);
            }
        }]);

        return App;
    }(_react["default"].Component);

;

_reactDom["default"].render(_react["default"].createElement(App, null), document.getElementById('root'));
复制代码

主要是 var App 开始的东西,可以看出这是一个立即执行函数,函数的参数是React.Componnet,函数内部写了一个名为App的构造函数,最后返回出这个构造函数。

其中的_inherits(App, _React$Component);这个函数是babel自行封装的继承。

_createClass(App, [{
    key: "componentDidMount",
    value: function componentDidMount() {
            console.log('component did mount');
    }
}, {
    key: "render",
    value: function render() {
        return _react["default"].createElement("div", null, this.state.name);
    }
}]);
复制代码

这个函数的作用是将第二个参数那个数组中的东西一个一个添加到构造App构造函数的原型链上。

最后调用ReactDOMrender方法,可以看出这其中的第一个参数是调用了React.createElement方法,包括在App组件中的render也是调用了这个函数。那么重点来了,看一下这个React.createElement方法

React.createElement

大家都知道,React中的一个关键词就是jsx,babel在解析jsx语法时,就会调用React.createElement方法将jsx转换为一个ReactElement对象。在源码/packages/react/src/ReactElement.js文件中。

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 */
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;

  // 将 config 中的配置添加到 props 中
  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;
    // 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.
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  // 添加默认的props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

  if (__DEV__) {
    /**/
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
复制代码
  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;
    // 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];
      }
    }
  }
复制代码

这是将传入的config参数添加到props中.

  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }
复制代码

arguments是一个关键字,存储着函数的参数,是一个类数组对象,可以使用一些基本的数组api但是他本质上不是一个数组而是一个对象。使用arguments.length - 2除去前两个参数剩下的就都是子节点了。这边对数组的长度进行了判断,可以发现只有一个子节点的时候props.children值是一个对象,多个子节点时,值为一个数组,上面的那个例子体现不出这个,换成下面的例子然后在babel一下就可以看出这个参数。

// 添加一个div节点
ReactDom.render(
    <App>
        <div>Hello World</div>
    </App>,
    document.getElementById('root')
);
// 转换成babel之后的结果
_reactDom["default"].render(
    _react["default"].createElement(
        App,
        null,
        _react["default"].createElement("div", null, "Hello World")
    ),
    document.getElementById('root')
);
复制代码

这边可以看出又新加了一个createElement

解析完children之后,添加进props对象里,在往下走,

  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
复制代码

这一块是来解析defaultProps

return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
复制代码

最终这个createElement方法会返回一个ReactElement对象。

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    // 独特的唯一标示 表明这一个react元素
    $$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.
    // 记录负责创建此节点的组件
    _owner: owner,
  };

  if (__DEV__) {
    /**/
  }

  return element;
};
复制代码

返回一个element对象,需要注意的是这边有一个标示符?typeof,表明这是一个React.Element对象,源码中在英文注释

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.

大概意思就是说以后不在坚持使用class来创建了,所以不要new调用它,同样的instancof也是没有效果的,取而代之的是使用一个标识符?typeof值为一个sybmol对象Symbol.for('react.element')

在这个文件的下方还有一个方法用来校验一个对象是不是React.Element

/**
 * Verifies the object is a ReactElement.
 * See https://reactjs.org/docs/react-api.html#isvalidelement
 * @param {?object} object
 * @return {boolean} True if `object` is a ReactElement.
 * @final
 */
export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}
复制代码

直接比对的?typeof属性

Component PureComponent

写一个React组件时,需要继承React.Component,而PureComponentComponent的区别在于,使用PureComponent可以在一定程度上优化React的性能,因为PureComponent内部是对preProps nextProps做了浅对比。

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}
复制代码

这边主要是updater有一个更新队列的东西,下次再说,之后在Component的原型上附加一系列的方法。而PureComponent是继承的Component

function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
复制代码

不同的地方在于PureComponent添加了一个属性用来表识该实例是一个PureComponent pureComponentPrototype.isPureReactComponent = true;

下一篇应该是记录ReactDom.render方法