什么是生命周期钩子?
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
理解:支持用户定义函数,并在这个函授内部可以访问到实例的属性(设置this),这个函数会在实例实例化的过程中被调用。
生命周期钩子函数有哪些?
名称 | 触发时机 |
---|---|
beforeCreate | 在实例初始化之后调用。 |
created | 在实例创建完成后被立即调用。 |
beforeMount | 在挂载开始之前被调用。 |
mounted | 实例被挂载后调用。 |
beforeUpdate | 数据更新时之后,虚拟DOM更新和页面重新渲染之前调用。 |
updated | 数据更改导致的虚拟DOM更新和页面重新渲染之后调用。 |
beforeDestroy | 实例销毁之前调用。 |
destroy | 实例销毁后调用。 |
activated | 被 keep-alive 缓存的组件激活时调用。 |
deactivated | 被 keep-alive 缓存的组件停用时调用。 |
简单应用
beforeCreate
此函数被调用的时候,内部this还有没有修改,即无法直接通过实例加点的方式访问data,props,methods,computed
等。
那么此时能干点什么?实际开发中我只有用到一次,因为能在这里做的事情,如果不涉及到Vue实例,那么完全可以在Vue实例之前完成,比如根据url参数设置Vue实例的初始值。
var query = geturlquery();
var data = {
num:query.num ? query.num : 0
}
new Vue({
el:"#app",
data:data
})
第二种,对vue
实例配置进行修改,原则上是不被允许的允许的,因为容易出错,你传的opts
已经过Vue
的处理过挂到$options
上了,所以你这个时候去处理很容易出问题。
例如:你的data
已经被处理成了一个函数,那么我们按照Vue
的表现尝试下修改.
new Vue({
el:"#app",
data:{
test:"test"
},
beforeCreate() {
console.log(this);
this.$options.data = function(){
return {
title:"title"
}
}
},
})
这里当然是成功了的,实例化之后只有一个title
数据在$data
上,但是请不要使用这方法,这种方式我们按照内部运行机制进行修改的,当然理所应该是可以的。
但是请注意,我们并不能保证这种内部运行机制是不变的,并且Vue
也没有保证这个,Vue
能保证的是你传入的opts
和完成实例化的实例的表现是固定不变的。
而这种不规范的改变,在版本晋升之后是否能保证是谁也不能保证的。
下面说说我用到的情况,是结合vue-router一起使用的,在spa页面中,我们在beforeCreate
这里已经可以通过this.$router
获取到跳转之后的router
和它携带的参数,可以通过参数进行一些控制操作,如果不允许访问,直接跳转离开。
beforeCreate(){
if(!this.$route.params.allowed){
this.$router.go(-1)
}
},
这个操作跟上面的通过query
进行操作类似,但是相比在外部进行操作,this.$router
无疑是最方便获取路由信息的方式,而相对后面的其他周期,beforeCreate
是最先的周期,能更早的进行判断。
created
在这个周期里面,我们传入的数据和方法,已经被初始化到实例上了,并且已经完成了数据监测的双向绑定机制,所以这个阶段可以进行操作数据和使用方法。
所以,个人认为这个阶段我们去请求初始化的ajax数据是最好的,原因是最早,尽可能的减少用户等待时间。
如果再mounted之前能返回,我们就直接看见数据,不用走更新流程。
如果再mounted之后,走数据改变更新视图流程。
至于其他的,能在上个钩子中的做的事情,这个都可以做。
但是注意这个时候实例还没有挂载,所以不存在DOM的,基于DOM的一些操作和插件的初始化这里不能进行。
beforeMount
我一直感觉是这个很尴尬的钩子,这钩子相比前一个钩子函数。
首先多了一个$el
属性,但是这个$el
跟我们常使用的那个$el
还有点不一样,他是原始传入的那个DOM
,目前还不是我们最终挂载到页面的那个DOM
,看下面代码:
new Vue({
el:"#app",
data:{
test:"test"
},
beforeMount() {
console.log(this.$el);
},
})
// <div id="app">{{test}}</div>
有谷歌调试经验的应该知道,谷歌控制台打印出来的DOM的,鼠标移上去的时候会标记当前页上的位置,但是这个没有。
所以这个$el
是将来挂载的时候,被替换的那个DOM。那么问题来了,我能拿它干点啥,毕竟下个周期就要被替换了。
除了这个之外,还有了一个重要函数,_render
函数,这个函数根据html模板生成的渲染函数,最后会生成DOM渲染页面过程会调用这个函数。那么问题又来了,我难道还能修改这个函数吗?
new Vue({
el:"#app",
data:{
test:"test"
},
beforeMount() {
this._render = () => this.$createElement('h1', null, 'It works!')
},
})
嗯,事实证明我们可以修改这个函数,那么问题又来了,我们那么大段的模板代码编译成了一个渲染函数,我们这里改它干什么?如果说要手写_render
函数,也可以在传入的opts
中增加一个render
函数就行了。
这个阶段多的属性和方法,暂时没想到有什么用处。
mounted
这个生命钩子被调用的时候,代码Vue实例已经成功挂载在页面上了,$el
已经替换成生成的DOM,并且替换掉了页面的原来的$el
,并且对应的数据和视图形成双向绑定。请注意这个过程是替换,所以官方要求我们,不要直接初始化在body
元素上。
此时,可以访问所有Vue
提供给我们的方法和属性了,可以访问到生成之后的DOM了。
基于的DOM的插件也可以在这里初始化了。
beforeUpdate
首先,需要确定的是,Vue的数据更新导致视图更新是一个异步的过程,在一次事件循环过程中,无论改变了多少个数据或同一个数据改变了多少次,最终的只会执行一次视图更新。
而beforeUpdate是在视图更新前调用,所以在这里我们能取到所有改变的数据,并且可以进行最后一次修改。
updated
这个生命周期钩子会在视图更新之后调用,也就是说,在这里钩子中,我们能取到更新之后的生DOM元素,所以在一些基于DOM的插件中,这个周期可以刷新插件,使之监听到新增的DOM和删除的DOM。
当我们调用
$destroy
,会触发两个生命周期,beforeDestroy
和destroyed
。
beforeDestroy
当调用$destroy
,就会立即调用这个钩子,所以在这个阶段,我们还可以通过正常操作Vue。
网上大多数推荐在这周期进行解绑$on
监听的事件(特别是使用了eventhub),清楚定时器,解绑dom事件。
当然,这些处理是完全可以的,但是相对于这些处理,我更倾向于把这些操作放到destroyed
中做,因为那个是时候才真正意味着这个实例的销毁,那么处理Vue没给我们处理的一些事件,定时器的解绑是不是更合理一点?
那么这个周期能干什么?个人认为,这个周期官方特意注明了。这个时候,还能正常的操作Vue,而Vue触发销毁之后是不会中断的,所以这个时候我们的能干什么?用狼人杀的逻辑,我已经被砍了,那么当我还在的时候(最后能正常发言的时候),能做的只有留遗言,通知跟我关系的(手动监听了我的其余实例或者方法),告诉他们我要销毁了,我这边的联系我会解除,你们赶紧解除跟我的关联吧啊(不是vue自带的),是不是合理一点。
destroyed
实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
但是实际上
callHook(vm, 'destroyed');
// turn off all instance listeners.
vm.$off();
这周周期钩子触发完成之后才会解绑事件,但是需要特别注意的是,这里解绑的是,父子组件之间的自定义事件,如果给一个DOM绑定一个click事件,即使这个事件是用过@
绑定的,这个事件也不会被销毁,看到这里大家也就清楚,原生事件不会被解除,那么已经渲染到页面上的DOM那么就更不会自动回收移除了。
那么这个$destroy()到底干了什么呢?解除Vue组件之前的父子关联,解除自定义事件的监听,解除watch的观察,最重要的是,解除页面和实例的双向绑定,最后解除Vue实例化过程对Vm的指向占用,而js的收回机制,当一个对象没有指向占用的时候,会被自动回收。
所以这阶段,我们需要做的是对这个$destroy
方法进行补充操作,
- 对初始化的基于DOM的插件进行销毁,解除实例存放的变量的占用
- 需要保存DOM的时候手动解绑DOM的实践监听
- 不需要保存DOM就直接移除掉所有DOM
还有两个在使用
vue-router
并且使用keepalive
缓存路由页面的才会触发的钩子函数deactivated
和activated
。
activated
比较的经典使用经常就是缓存滚动条的位置。大家都知道,vue-router
切换不同路由的时候,即使我们使用keey-alive
也不会缓存滚动的位置,所以这个时候需要只要在滚动的时候记住scrollTop
存放在当前实例中,因为我们使用的缓存,所以当切换到其他路由的时候这个实例也不会销毁,当再次激活的时候,会触发这个周期,我们只需要吧scrollTop
赋值给滚动元素,就简单的缓存的滚动条的位置。
// just like this
mounted () {
this.scrollEl.onscroll = () => {
this.scrollTop = this.scrollEl.scrollTop;
}
},
activated () {
this.$refs.scrollEl.scrollTop = this.scrollTop;
}
另一个经典的使用场景是,缓存页面中,根据路由参数或者状态局部刷新路由页面的数据。
deactivated
当这个周期被激活的时候,有一个意思的现象,在这个周期访问this.$router
会发现已经更新成跳转之后的路由而不是当前实例的路由,而this
访问到还是当前实例。那么周期,我们就可以根据去往的路由页面,对当前实例进行一些操作。
比如,当前页面是详情,详情下面还有一个编辑页,那么当前页面如果从列表中进来的时候设置了缓存,如果进入编辑,我们也需要缓存当前页面,返回的时候activated
周期进行局部刷新。而返回的到列表的时候,需要销毁这个页面的缓存,就可以这样。
activated(){
this.loadDetail();
},
deactivated() {
if(this.$route.name == 'index'){
this.removeKeepAlive('detail');
}
}
欢迎交流~