能否理解一个组件的生命周期是非常重要的,甚至可以说是最重要的内容,他是一切进阶的基石,下面我们就从angular1的生命周期开始吧。
angular1生命周期
angular1的生命周期在1.5之前是很晦涩的,controller、compile、preLink、postLink,compile的return函数。从字面上让人完全摸不着头脑,到了1.5增加了angular.component,这一问题有了很大改观,生命周期全部放到controller(controller不再作为一个生命周期钩子存在),controller就是一个class,实例函数$onInit、$onPostLink和$onDestroy组成了新的生命周期钩子,从字面上也容易理解了很多。$onInit代表编译前,在这里你可以对$scope做属性变更,和逻辑定义;$onPostLink代表编译后,这个时候子指令已完成了编译,你可以在这里获取到任意子指令实例并调用其方法;$onDestroy在当前scope被销毁时调用,在这里你需要对dom做销毁,以及可能的全局变量做清空。很简单就是组件编译前、组件编译后和组件销毁时。那么我们再看看react的生命周期钩子。
react生命周期
react的生命周期分为三大阶段,
- 插入dom(mount)
- 重新render(update)
- 销毁dom(unmount)
在定义class(组件)的时候其实就是在定义一个状态机,定义了各种状态的响应,这里包括这三大阶段的钩子以及事件响应,等待触发条件的满足。
1 插入dom(mount)
先来说下第一阶段,首次render触发时会执行以下钩子函数,
- getDefaultProps
getDefaultProps: function(){ return { /* 默认属性 */}; }复制代码
getInitialState
getInitialState: function(){ return { /* 初始状态 */}; }复制代码
这两个函数不难理解,目的是使代码结构更清晰,不需要在constructor里做过多的打底处理。
componentWillMount
这个方法在出现class XX extends React.Component
之后其实就可以用constructor
来代替了。对于angular1.5这和$onInit
也是类似的,同样也可以用constructor代替。render
这个函数其实和angular指令中的template属性(值对应一个函数)很相似,但是借助jsx,render比template属性强大实在太多,angular所提供的template不够灵活,在template函数中无法拿到$scope,自然我们就选择例如ngIf
这样的属性指令来做模版输出控制,该指令会在当前指令作用域下再创建一个子作用域,这使得我们在获取子组件的实例的时候会比较麻烦,ngRepeat
、ngSwitch
都是这样,angular1的初衷是不希望我们自己做字符串拼接,的确就算我们能拿到$scope,字符串拼接也实在不够优雅。
所以render函数直白点完成了两件事情:- 执行jsx表达式,生成最终插入dom容器节点的真实dom(这里不考虑虚拟dom的问题)
- 借助react提供的synthetic event system完成事件监听绑定
所有叶子组件的jsx模版都是由基础原生html标签、属性以及react所提供的代理属性组成的,因此如果你写如下一段模版代码:
render(){ return ( <div num=1 changehandler={this.changeHandler.bind(this)}/> ) }复制代码
num
和changehandler
属性会直接被忽略,如果你想在原生标签上使用自定义属性,请在属性前面添加data-
。componentDidMount
componentDidMount
和angular中的link或postLink很相似,但是它更让人放心,因为angular中如果你想在link中获取带有ngIf或ngRepeat的dom是获取不到的(因为会再transclude一次并等待下次脏数据校验才会插入dom,有兴趣的同学可以移步重复transclude和ngIf脏数据校验),因此在这里你可以放心的访问dom。小结:上述方法除了
render
,只会伴随第一次render执行一次。
2 重新render
当组件props和state(通过setState)发生变更时,会触发re-render
操作。因此这里我们把state和props变化分开来说。
在angular1中其实并没有多次触发的钩子,像前面说到的controller、compile等也都只执行一次,其实react的重新渲染在angular类似$watch,一般我们在$watch来实现dom的变更,这种命令式的修改dom当然是不推荐的,react采用了一种更彻底的方式,给人的感受就像又走了一遍第一次渲染一样,而你可以这样理解,相对于第一次,它可能仅仅变更了个别state或props,通过虚拟dom和diff算法,react帮助你高效的将变更应用于虚拟dom上最终re-render出新的dom树。
2.1 state变更
状态变更会触发以下几个钩子
- shoudComponentUpdate
该函数给用户一个机会使得其可以控制是否需要进行重新渲染,angular1并没有提供终止指令编译的钩子函数,这是react的高级特性了,主要针对性能优化,自己没有用过就不误导大家了。shouldComponentUpdate: function(nextProps, nextState){ return true }复制代码
componentWillUpdate
componentWillUpdate: function(nextProps, nextState){ // 为re-render做准备 }复制代码
为啥需要这个钩子呢,因为只有
shouldComponentUpdate
返回true才能确定一定会重新渲染,那么在这里我们可以对界面的提示UI做变更,比如之前为了获取state或props会因为异步请求出现loading,那么这个时候就可以隐藏loading。记住这里不能有任何会再次触发组件重新渲染的逻辑。componentDidUpdate
componentDidUpdate: function(prevProps, prevState){ // 可以访问渲染完成的dom }复制代码
这个没啥可说的,和
componentDidMount
是一样的。
2.2 props变更
componentWillReceiveProps: function(nextProps) {
this.setState({
// new state
});
}复制代码
不像state需要调用setState才会触发re-render,由父组件导致的任何属性变更都会触发re-render,不需要调用额外的方法。其实props变更所经历的生命周期钩子和state几乎一样,唯一不同是在shouldComponentUpdate之前增加了componentWillReceiveProps,用意也十分明显,我们可以在componentWillReceiveProps中通过setState对state做修改,比如组件内部的state是根据某个或多个props计算得出的,这样我们在调用setState时候也不会造成额外的re-render被触发,深入理解react
3 销毁dom(unmount)
当我们改变UI布局或者使用接口删除组件树上某个组件时就会触发组件的componentWillUnmount
方法,在这里你可以取消事件监听或定时器,以及其他会造成全局引用的变量。
class App extend Component {
componentWillUnmount(){
// unregister event or clear timer
}
}
render(<App />, document.getElementById('root')
function ummount(){
React.unmountComponentAtNode(document.getElementById('root'));
}复制代码
4 结束
这篇其实和angular1的对比不是太多,但其实大概的生命周期和angular1还是大同小异,特别是re-render这块更有条理也更有性能优势。react与angular1的对比先暂告一段落,后面将开始redux的学习,同时会对官方的demo做详细讲解。
文中如有错误请大家及时纠正,谢谢。