根组件
每一个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.