虽然经常使用React、redux编写SPA,可是这一块是如何运做,应该如何优化,仍是比较困扰,最近开始阅读程墨的《深刻浅出React和Redux》,结合以前读过的React源码和相关源码的文章后,打算从源码的角度,解释下书中的一些内容。html
书中有一段话,关于组件从初始化到挂载通过的声明周期:
流程:git
componentDidMount
以前调用的!!怪不得!每次在componentDidMount
里调用异步的时候,render
里面的object.xxx
从报错!原来在componentDidMount
以前,已经render
过一次,而这个是后object
是个null
。很简单的一串代码,以下:github
class R extends Component {
constructor(props) {
super(props);
console.log('constructor');
this.state = {value: props.value}
}
componentWillMount() {
console.log('will mount');
}
componentDidMount() {
console.log('did mount');
}
render() {
console.log('render');
return (<div>{this.state.value}</div>);
}
}复制代码
固然,浏览器暂时仍是无法识别ES6的语法的,因此须要经过babel编译。通过babel编译后以下:redux
var R = function (_Component) {
_inherits(R, _Component);
function R(props) {
_classCallCheck(this, R);
var _this = _possibleConstructorReturn(this, (R.__proto__ || Object.getPrototypeOf(R)).call(this, props));
console.log('constructor');
_this.state = { value: props.value };
return _this;
}
_createClass(R, [{
key: 'componentWillMount',
value: function componentWillMount() {
console.log('will mount');
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
console.log('did mount');
}
}, {
key: 'render',
value: function render() {
console.log('render');
return React.createElement(
'div',
null,
this.state.value
);
}
}]);
return R;
}(Component);复制代码
从上面来看,其实我比较开心且兴奋的看到了闭包和*原型、继承,不清楚的小伙伴能够github.com/mqyqingfeng… 这篇文章补补。
再来看看constructor,从编译后的代码来看constructor并非React原型的某个方法,而是babel转译后的这个下面的这块函数。浏览器
function R(props) {
_classCallCheck(this, R);
var _this = _possibleConstructorReturn(this, (R.__proto__ || Object.getPrototypeOf(R)).call(this, props));
console.log('constructor');
_this.state = { value: props.value };
return _this;
}复制代码
根据上面复习的原型、继承, 这是个构造函数。super
对应的是_possibleConstructorReturn
.bash
这样,咱们就明白了,为何首先会调用constructor
了:constructor
并非React组件的原型函数,而是babel编译后的一个构造函数。因此当实例化组件的时候,天然会先调用ES6中的constructor
了。babel
组件是如何插入到DOM中呢?先看看ES代码:闭包
ReactDOM.render(
<R />,
document.getElementById('example')
);复制代码
调用的是ReactDOM.render
追踪源码,实际调用的是ReactMount.render
。一层层追踪后以下图所示:app
图片中黄色数字标识:步骤顺序。蓝色框表示里面剩下的步骤都是在performInitialMount
完成的。
步骤按深度优先方式看。异步
从图中,咱们能看到React组件周期,最先开始与performInitialMount
这个函数里。其次是render函数,当执行完performInitialMount
后,跳出环境栈,接着执行componentDidMount
函数。
所以:最后的顺序是constructor
-> componentWillMount
-> render
-> componentDidMount
书中还提到一句话:
render
函数并不作实际的渲染动做,他只是返回一个JSX描述的结构。render
函数被调用完以后,componentDidMount
函数并非会被马上调用。componentDidMount
被调用的时候,render
函数返回的东西已经引起了渲染,组件已经被『装载』到了DOM树上
最直观的从console.log
来看
ReactElement
。原来
render
返回的jsx的结构就是个
ReactElement
。其实,从babel那翻译过来的js也能看出,
render
返回的是一个
ReactElement
{
key: 'render',
value: function render() {
console.log('render');
return React.createElement(
'div',
null,
this.state.value
);
}复制代码
想要解释第二句话,咱们得更加仔细的分析源码:
class R extends Component {
constructor(props) {
super(props);
console.log('constructor');
this.state = {value: props.value}
}
componentWillMount() {
console.log('will mount');
}
componentDidMount() {
console.log('did mount');
}
render() {
console.log('render');
return (<div>{this.state.value}</div>);
}
}
ReactDOM.render(
<R />,
document.getElementById('example')
);复制代码
首先,React会在R
上面再包裹一层,叫作_topLevelWrapper
的层,这个对象建立出来的是一个ReactCompositeComponentWrapper
对象,他基于ReactCompositeComponent
,以下:
var ReactCompositeComponentWrapper = function (element) {
this.construct(element);
};
_assign(ReactCompositeComponentWrapper.prototype, ReactCompositeComponent.Mixin, {
_instantiateReactComponent: instantiateReactComponent
});复制代码
给instantiateReactComponent
打日志,获得以下:
最外层组件的TopLevelWrapper是个null,_renderedComponent
是组件R
,在下一步:
第二个是打印出来的R组件对应的ReactComponent对象,图中能看到这个组件的下一个组件是ReactDOMComponent
。最后的打印出来的以下:
在图1上稍微完善了下,另一个维度的大体调用流程以下图:
为何说大体调用流程图,由于,由于在this.performInitialMount
函数里有一个递归的过程。而后React组件除了ReactCompositeComponent
类以外,还有上面提到的ReactDOMComponent
、ReactEmptyComponent
和另一个内部类(这个类笔者不知道)。而图中,咱们只是画了一个ReactCompositeComponent
。
接下来,咱们就说说这个的调用流程把:
ReactMount内部的调用方式,图上自认为画的已是至关清楚了,因此这里不作详细说明。
从图中第3.1步骤开始提及:
一、在ReactMount中的mountComponentIntoNode
里,调用了ReactReconciler.mountComponent
方法,方法中的wrapperInstance
这个时候,是ReactCompositeComponentWrapper
对象。返回一个markup(暂时不说)。那咱们看看ReactReconciler.mountComponent
这个方法是什么。
二、在ReactReconciler.mountComponent
方法中,调用了是internalInstance.mountComponent
方法,也就是说咱们调用了『第1步』中的ReactCompositeComponentWrapper
对象的mountComponent
方法,那咱们再去ReactCompositeComponentWrapper.mountComponent
看看。
三、ReactCompositeComponent
笔者理解为是一个组合组件,什么是组合组件呢,自认为就是把ReactDOMComponent
、ReactEmptyComponent
这两种包含到一块儿的组件。。。扯远了,看看ReactCompositeComponent.mountComponent
作了什么把。他调用了一个performInitialMount
方法。
四、那performInitialMount
方法干了个啥???进去一看!重点来了!!他先看看组件有没有componentWillMount
呀~有的话就调用。而后跳进_renderValidatedComponent
这个函数中去了!。好吧,那咱们就去_renderValidatedComponent
这个函数中看看。
五、一看发现,_renderValidatedComponent
这个函数又调用了_renderValidatedComponentWithoutOwnerOrContext
方法。
六、那就进去看看把,一看便知,_renderValidatedComponentWithoutOwnerOrContext
调用了组件的render
方法。var renderedComponent = inst.render();
,一看,render有个返回值!!!那这个返回值是什么呢!!!打出来一看,图三、4:
render
函数并不作实际的渲染动做,他只是返回一个JSX描述的结构(ReactElement)
七、带着这个ReactElement
结构,咱们跳出了_renderValidatedComponentWithoutOwnerOrContext
函数,返回了『第5步』后,在_renderValidatedComponent
中,继续返回了这个结构,返回到了第4步中的方法performInitialMount
中,执行到_renderValidatedComponent
以后。执行下面一句话:
this._renderedComponent = this._instantiateReactComponent(renderedElement);复制代码
八、看过代码的人,很清楚了_instantiateReactComponent
这个函数的主要做用就是根据不一样ReactElement
,返回不一样类型的ReactComponent
。接下来呢,在performInitialMount
又执行了
ReactReconciler.mountComponent(this._renderedComponent, .......);复制代码
好了,咱们又开始了『第二步』的流程。可是注意,这个时候,仍是在当前这个组件的函数栈中。
九、假设,咱们renderedElement
返回结构是图4的结构,这个时候的this._renderedComponent
那就是ReactDOMComponent
对象。那这时,ReactReconciler.mountComponent
里就会调用ReactDOMComponent.mountComponent
十、ReactDOMComponent.mountComponent
返回的是一个图5结构的同样的东东(后面会叫他markup)。
ReactElement
和
ReactCompositeComponent
就是不同,感受亲切多了!!废话少说!
十一、既然『第10步』的ReactDOMComponent.mountComponent
调用完了,咱们就返回到『第9步』performInitialMount
里,一看!执行完了,返回就是图5的markup。那么,这个函数出栈,回到『第3步』。
十二、ReactCompositeComponent
里执行完performInitialMount
以后,就会调用componentDidMount
,看看他有没有componentDidMount
这个函数。若是有的话,就会执行
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);复制代码
(通篇来看,这个调用的频率仍是挺高的,具体作什么,之后再说)
1三、ReactCompositeComponent.mountComponent
以后,ReactCompositeComponent.mountComponent
函数出栈,回到『第一步』:mountComponentIntoNode
。返回markup,以后。调用React._mountImageIntoNode
函数。这个函数里,匆匆扫了一下关键字:发现,就是将『图5』的markup结构,转化成了对应的html结构。里面有一个笔者认为的这么说的点睛之笔是setInnerHTML
。
综上所属:
咱们能够得出的结论是:
constructor
->componentWillMount
->render
->componentDidMount
render
函数返回的是一个jsx的ReactElement结构。
至于第三个:
render
函数被调用完以后,componentDidMount
函数并非会被马上调用。componentDidMount
被调用的时候,render
函数返回的东西已经引起了渲染,组件已经被『装载』到了DOM树上。
笔者还得看看~~ 不过相信,这句话确定是对的!要否则,怎么会出书,要否则为何叫作componentDidMount
,函数字面意思,就是render以后,先插入DOM,再调用componentDidMount
函数。看来,关键的地方在transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
这句上。
这也是接下来须要研究的。
限于文章不能写太长。先暂时这样,抛出几个待讨论的点,省的忘记。
感受不少话说的很重复,可是笔者就是想从不一样角度说地更仔细点。文章栗子有限,说的只是大概。若有说不明白的或者说错的地方,麻烦指出。由于,笔者是一个热爱学习,追求进步的北京打工的外地人!!!!!!