前言
在使用vue开发的过程中,我们经常会接触到生命周期的问题。接下来让我们了解一下这些在一个标准的工程项目中的生命周期:
- 根组件实例:8个 (beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed)
- 组件实例:8个 (beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed)
- 全局路由钩子:2个 (beforeEach、afterEach)
- 组件路由钩子:3个 (beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave)
- 指令的周期: 5个 (bind、inserted、update、componentUpdated、unbind)
- beforeRouteEnter的next所对应的周期
- nextTick所对应的周期
组件实例周期
beforeCreate
在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。1
2
3
4
5
6
7
8
9
10
11
12
13var vm = new Vue({
el: '#app',
data: {
message : "hello Vue!"
},
beforeCreate: function () {
console.group('beforeCreate 创建前状态===============》');
console.log("el:" + this.$el); //undefined
console.log("data:" + this.$data); //undefined
console.log("message:" + this.message) //undefined
}
})
tips:
此时组件的选项还未挂载,因此无法访问methods,data,computed上的方法或数据
created
实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
这是一个常用的生命周期,因为你可以调用methods中的方法、改变data中的数据,并且修改可以通过vue的响应式绑定体现在页面上、获取computed中的计算属性等等。1
2
3
4
5
6
7
8
9
10
11
12
13var vm = new Vue({
el: '#app',
data: {
message : "hello Vue!"
},
created: function () {
console.group('created 创建完毕状态===============》');
console.log("el:" + this.$el); //undefined
console.log("data:" + this.$data); // [object Object] //已被初始化
console.log("message:" + this.message) // hello Vue! //已被初始化
}
})
tips:
通常我们可以在这里对实例进行预处理。
在这里发ajax请求,值得注意的是,这个周期中是没有什么方法来对实例化过程进行拦截的。
因此假如有某些数据必须获取才允许进入页面的话,并不适合在这个页面发请求。
建议在组件路由钩子beforeRouteEnter中来完成。
beforeMount
在挂载开始之前被调用,相关的 render 函数首次被调用:render() {return (…)},或template属性
挂载前$el的值为’虚拟’的元素节点,如下图红框:1
2
3
4
5
6
7
8
9
10
11
12
13
14var vm = new Vue({
el: '#app',
template: '<div id="app">{{ message }}</div>',
data: {
message : "hello Vue!"
},
beforeMonut: function () {
console.group('beforeMonut 挂载前状态===============》');
console.log("el:" + this.$el); //[object HTMLDivElement] <div id="#app">{{message}}</div> //已被初始化
console.log("data:" + this.$data); // [object Object] //已被初始化
console.log("message:" + this.message) // hello Vue! //已被初始化
}
})
tips:
渲染优先级:render函数 > template属性 > new Vue函数外部html
vue编译过程——把tempalte编译成render函数的过程。
mounted
挂载后’虚拟’的Dom节点被真实的Dom节点替换,如下图红框:
1 | var vm = new Vue({ |
tips:
1.在这个周期内,对data的改变可以生效。但是要进下一轮的dom更新,dom上的数据才会更新。
2.这个周期可以获取 dom。
3.beforeRouteEnter的next的钩子比mounted触发还要靠后
4.指令的生效在mounted周期之前
beforeUpdate
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。
在控制台里输入vm.message = ‘数据更新’后,如下图:
由此可见,当data数据变化时会触发updated重新渲染DOM
beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。
tips:
这一步还可以用this来获取实例。
一般在这一步做一些重置的操作。比如清除掉组件中的 定时器 和 监听的dom事件
destroyed
Vue 实例销毁后调用。Vue 实例的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
activated
keep-alive生命周期钩子函数:当组件激活的时候调用,路由切换时 keep-alive包含route-view 触发
deactivated
同上
vue实例生命周期总结
beforeCreate(此时date、method和el均没有初始化,可以在此加载loading事件)
created(此时date和method初始化完成,在这添加结束loading事件,但是DOM节点并没有挂载,判断是否有el节点,如果有则编译template,如果没有则使用vm.$mount创建一个默认节点,此时可以在DOM渲染之前进行数据的初始化和method的自执行等)
beforeMount(编译模板,并且将此时在el上挂载一个虚拟的DOM节点)
mounted(编译模板,且将真实的DOM节点挂载在el上)
beforeUpdate(在数据有更新时,进入此钩子函数,虚拟DOM被重新创建)
updated(数据更新完成时,进入此钩子函数)
beforeDestory(组件销毁前调用,此时将组件上的watchers、子组件和事件都移除掉)
destoryed(组件销毁后调用)
一般一个Vue实例初始化时,只会执行前四个钩子函数
全局路由钩子
作用于所有路由切换,一般在main.js里面定义
router.beforeEach
1 | router.beforeEach((to, from, next) => { |
一般在这个钩子的回调中,对路由进行拦截。
比如,未登录的用户,直接进入了需要登录才可见的页面,那么可以用next(false)来拦截,使其跳回原页面。
值得注意的是,如果没有调用next方法,那么页面将卡在那。
next的四种用法
- next() 跳入下一个页面
- next(‘/path’) 改变路由的跳转方向,使其跳到另一个路由
- next(false) 返回原来的页面
- next((vm)=>{}) 仅在beforeRouteEnter中可用,vm是组件实例。
router.afterEach
1 | router.afterEach((to, from) => { |
在所有路由跳转结束的时候调用,和beforeEach是类似的,但是它没有next方法
组件路由钩子
和全局钩子不同的是,它仅仅作用于某个组件,一般在.vue文件中去定义。
beforeRouteEnter
1 | beforeRouteEnter (to, from, next) { |
这个是一个很不同的钩子。因为beforeRouterEnter在组件创建之前调用,所以它无法直接用this来访问组件实例。
为了弥补这一点,vue-router开发人员,给他的next方法加了特技,可以传一个回调,回调的第一个参数即是组件实例。
一般我们可以利用这点,对实例上的数据进行修改,调用实例上的方法。
我们可以在这个方法去请求数据,在数据获取到之后,再调用next就能保证你进页面的时候,数据已经获取到了。没错,这里next有阻塞的效果。你没调用的话,就会一直卡在那
beforeRouteLeave
1 | beforeRouteLeave (to, from, next) { |
在离开路由时调用。可以用this来访问组件实例。但是next中不能传回调。
beforeRouteUpdate
这个方法是vue-router2.2版本加上的。因为原来的版本中,如果一个在两个子路由之间跳转,是不触发beforeRouteLeave的。这会导致某些重置操作,没地方触发。在之前,我们都是用watch $route来hack的。但是通过这个钩子,我们有了更好的方式。
directives 指令周期
绑定自定义指令的时候也会有对应的周期。
这几个周期,比较常用的,一般是只有bind。
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:1
2
3Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
bind
只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted
被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
实际上是插入vnode的时候调用。
update
被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
慎用,如果在指令里绑定事件,并且用这个周期的,记得把事件注销
componentUpdated
指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind
只调用一次,指令与元素解绑时调用。
Vue.nextTick、this.$nextTick
1 | created () { |
nextTick方法的回调会在dom更新后再执行,因此可以和一些dom操作搭配一起用,如 ref。
非常好用,可以解决很多疑难杂症。
场景:
你用ref获得一个输入框,用v-model绑定。
在某个方法里改变绑定的值,在这个方法里用ref去获取dom并取值,你会发现dom的值并没有改变。
因为此时vue的方法,还没去触发dom的改变。
因此你可以把获取dom值的操作放在vm.$nextTick的回调里,就可以了。