每个React项目都会有一个根组件(固然是针对单页面的项目)。好比咱们常常使用的叫作 App的组件。html
ReactDOM负责将根组件挂载在指定的DOM元素上,通常咱们指定的是 id为app的div上。react
React中咱们直接使用的jsx语法来写组件,可是,浏览器是不认识这些东西的,因此要想将组件真正的挂载在真实DOM上就须要对其进行转译。浏览器
就是咱们原始的写法,采用jsx语法写成的组件,这些内容是定义在组件的render方法内。 好比:bash
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内的内容明显不适合直接挂载,这就须要处理了。babel
咱们将上边的App组件使用babel转译一下,app
"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中的真实样子。dom
注意:组件只有在Render方法执行的时候才会去构建Element。 这里ReactDOM也有一个render方法。以下:函数
ReactDOM.render(<App />, document.getElementById("app"));
复制代码
固然,它render是App组件自己也就是:ui
createElement(App, null)
复制代码
参数 App则是上边的App变量。 咱们先来看看App自己被建立为Element的样子:this
$$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的样子。
接下来咱们摘取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…)的文章内又说明。
咱们这里就看一下挂载实例张什么样子。
_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会递归的将组件内的全部的一切都实例化为挂载实例。即便是一段纯文本也会有本身的挂载实例。
每个挂载实例都会有本身的mountComponent方法,这个方法返回一个markup,全部的markup组合起来即是一个形式上的饿DOM树。 关于marku请参考文章.
到此为止,React将咱们写的jsx代码转换为了能够直接挂载的markup。组件的转换之旅也就到此结束了。这一系列的过程,React作了不少的事情,好比,提取定义的事件注册一下等。
这是很重要的一条线:
jsx-->babel转译为 createElement方法的--->生成 Element---> 生成挂载实例--->挂载的时候生成 markup.