关于组件

598 阅读5分钟

根组件

每一个React项目都会有一个根组件(当然是针对单页面的项目)。比如我们经常使用的叫做 App的组件。

ReactDOM负责将根组件挂载在指定的DOM元素上,一般我们指定的是 id为app的div上。

React中组件的转译之路。

React中我们直接使用的jsx语法来写组件,但是,浏览器是不认识这些东西的,所以要想将组件真正的挂载在真实DOM上就需要对其进行转译。

①JSX组件

就是我们原始的写法,采用jsx语法写成的组件,这些内容是定义在组件的render方法内。 比如:

    import React from "react";
    import ReactDOM from "react-dom";
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
        this.state = {
          count: 1
        };
      }
      componentWillMount() {
        this.setState({
          count: 13
        });
      }
      handleClick() {
        this.handleState();
      }
      handleState() {
        this.setState({
          count: this.state.count + 1
        });
      }
      render() {
        return <div onClick={this.handleClick}>this is app {this.state.count}</div>;
      }
    }
    
    export default App;
 

我们知道,要挂载在真实DOM上的只有render内的内容。而render内的内容明显不适合直接挂载,这就需要处理了。

②bable转译

我们将上边的App组件使用babel转译一下,

    "use strict";

    var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
    
    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
    
    var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
    
    var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/possibleConstructorReturn"));
    
    var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/getPrototypeOf"));
    
    var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/assertThisInitialized"));
    
    var _inherits2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/inherits"));
    
    var _react = _interopRequireDefault(require("react"));
    
    var _reactDom = _interopRequireDefault(require("react-dom"));
    
    
    var App =
    /*#__PURE__*/
    function (_React$Component) {
      (0, _inherits2["default"])(App, _React$Component);
    
      function App(props) {
        var _this;
    
        (0, _classCallCheck2["default"])(this, App);
        _this = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(App).call(this, props));
        _this.handleClick = _this.handleClick.bind((0, _assertThisInitialized2["default"])(_this));
        _this.fileChange = _this.fileChange.bind((0, _assertThisInitialized2["default"])(_this));
        _this.state = {
          count: 1
        };
        return _this;
      }
    
      (0, _createClass2["default"])(App, [{
        key: "componentWillMount",
        value: function componentWillMount() {
          this.setState({
            count: 13
          });
        }
      }, {
        key: "handleClick",
        value: function handleClick() {
          this.handleState();
        }
      }, {
        key: "handleState",
        value: function handleState() {
          this.setState({
            count: this.state.count + 1
          });
        }
      }, {
        key: "render",
        value: function render() {
          return _react["default"].createElement("div", {
            onClick: this.handleClick
          }, "this is app ", this.state.count);
        }
      }]);
      return App;
    }(_react["default"].Component);
    
    _reactDom["default"].render(_react["default"].createElement(App, null), document.getElementById("app"));

如上所示,这才是React组件在React中的真实样子。

③自定义组件的生成lement

注意:组件只有在Render方法执行的时候才会去构建Element。 这里ReactDOM也有一个render方法。如下:

    ReactDOM.render(<App />, document.getElementById("app"));

当然,它render是App组件本身也就是:

    createElement(App, null)

参数 App则是上边的App变量。 我们先来看看App本身被创建为Element的样子:

    $$typeof: Symbol(react.element)
    type: ƒ App(props)
    key: null
    ref: null
    props: {}
    _owner: null
    _store: {validated: false}
    _self: null
    _source: null
    __proto__: Object

如上所示,便是一个自定义组件,App组件被转换为Element的样子。

④类HTMl标签内容生成Element

接下来我们摘取App组件自己的render方法来看看,如下。

    {
        key: "render",
        value: function render() {
          return _react["default"].createElement("div", {
            onClick: this.handleClick
          }, "this is app ", this.state.count);
        }
    }

这个render方法也是调用了createElement方法来创建一个Element。参数有三个:

第一个是:React规定每一个组件的render函数返回的内容只允许有一个(HTML标签)。而这个就是第一个参数,用来当做真正的HTMl标签来创建HTMl元素。

第二个是:一个config,也就是组件内的jsx上写的属性,包括方法,样式等。

第三个极其之后的:不限个数的参数,表示组件内包含的内容。可以是文本可以是html标签,可以是自定义的另外的组件。

Babel转译之后,调用特定的方法来将组件生成为Element。先来看看Element是什么样子的

    {
        $$typeof: Symbol(react.element)
        type: "div"
        key: null
        ref: null
        props: {children: Array(2), onClick: ƒ}
        
        _owner: ReactCompositeComponentWrapper {_currentElement: {…}, _rootNodeID: ".0", _instance: App, _pendingElement: null, _pendingStateQueue: null, …}
        
        _store: {validated: false}
        _self: null
        _source: null
        __proto__: Object
    }
    

注意:自定义组件和类html标签生成的Element是可以互相嵌套的,到最后就会形成一个树形的结构,其形式就如同DOM树的另外一种表示方法。每一个自定义组件,每一个自定义组件内的类似HTMl标签的内容都会有自己的Element。如此层层嵌套。

所以,简单的来说,Element就是描述了一种DOM树结构。当然,只是描述,而和真正的DOM树差异很大,但是,完全可以根据这个描述来创建一个真实的DOM树。

⑤挂载实例

组件被转换为Element并不是结束,React会将Element再次进行转换为挂载实例

我们先来看看App 组件 Element 的挂载实例。

    _reactDom["default"].render()

上边这个方法被调用的时候,是开始渲染的时刻。后边的文章会详细的讲述,这里我们就简单的来说一下。 在上边这个方法调用的时候最终会调用下边这行代码。 参数 nextElement 就是要实例的 Element。

    componentInstance = instantiateReactComponent(nextElement, null);

ReactDOM.render方法在挂载根组件的时候会调用上边的方法,这是一个递归的过程,将根组件包含的所有的内容都选择不同的方式进行实例化。这一部分在接下来的文章 [Element实例化] (juejin.im/editor/draf…)的文章内又说明。

我们这里就看一下挂载实例张什么样子。

⑥App的挂载实例

    _currentElement:{
        $$typeof: Symbol(react.element)
        type: ƒ App(props)
        key: null
        ref: null
        props: {}
        _owner: null
        _store: {validated: false}
        _self: null
        _source: null
        __proto__: Object
    }
    _rootNodeID: ".0"
    _instance: App {props: {…}, context: {…}, refs: {…}, updater: {…}, handleClick: ƒ, …}
    _pendingElement: null
    _pendingStateQueue: null
    _pendingReplaceState: false
    _pendingForceUpdate: false
    _renderedComponent: ReactDOMComponent {_tag: "div", _renderedChildren: {…}, _previousStyle: null, _previousStyleCopy: null, _rootNodeID: ".0", …}
    _context: {__validateDOMNesting_ancestorInfo$30mp078jcnw: {…}}
    _mountOrder: 2
    _topLevelWrapper: ReactCompositeComponentWrapper {_currentElement: {…}, _rootNodeID: ".0", _instance: TopLevelWrapper, _pendingElement: null, _pendingStateQueue: null, …}
    _pendingCallbacks: null
    _mountIndex: 0
    _mountImage: null
    _isOwnerNecessary: false
    _warnedAboutRefsInRender: false
    __proto__: Object

挂载实例分为不同的类别参考文章。这里是自定义组件的挂载实例。我们之前说过,React会递归的将组件内的所有的一切都实例化为挂载实例。即使是一段纯文本也会有自己的挂载实例。

⑥markup

每一个挂载实例都会有自己的mountComponent方法,这个方法返回一个markup,所有的markup组合起来便是一个形式上的饿DOM树。 关于marku请参考文章.

最后

到此为止,React将我们写的jsx代码转换为了可以直接挂载的markup。组件的转换之旅也就到此结束了。这一系列的过程,React做了很多的事情,比如,提取定义的事件注册一下等。

这是很重要的一条线:

jsx-->babel转译为 createElement方法的--->生成 Element---> 生成挂载实例--->挂载的时候生成 markup.