组件生命周期
在 Vue
中,每个组件都有自己的生命周期,所谓生命周期,指的是组件自身的一些方法(或者叫作钩子函数),这些方法在特殊的时间点或遇到一些特殊的框架事件时会被自动触发。Vue
组件的生命周期如图3-1所示。


可以看到,在 Vue
组件的整个生命周期中会有很多钩子函数可供使用,在生命周期不同的时刻可以执行不同的操作。下面列出所有的钩子函数:
beforeCreate |
beforeUpdate |
activated |
errorCaptured |
created |
updated |
deactivated |
|
beforeMount |
beforeUnmount |
renderTriggered |
|
mounted |
unmounted |
renderTracked |
主要 Vue
生命周期事件被分为两个钩子,分别在事件之前和之后调用,vue
应用程序中有 4 个主要事件(8 个钩子):
-
创建 ---- 在组建创建时执行
-
挂载 ----
DOM
被挂载时执行 -
更新 ---- 当响应数据被修改时执行
-
销毁 ---- 在元素被销毁之前立即执行
在学习组件的生命周期之前,建议读者先编写一个简单的页面,以实际体验每个钩子函数的触发顺序和时机。运行之后,可以在控制台中看到具体的触发时机,当然某些方法在特定的场景才会触发。以下是具体的示例:
const vm = Vue.createApp({
data(){
return {
message: 'Vue组件的生命周期'
}
},
beforeCreate() {
console.log('------beforeCreate------');
},
created() {
console.log('------created------');
},
beforeMount() {
console.log('------beforeMount------');
},
mounted() {
console.log('------mounted------');
},
beforeUpdate () {
console.log('------beforeUpdate------');
},
updated () {
console.log('------updated------');
},
beforeUnmount () {
console.log('------beforeUnmount------');
},
unmounted () {
console.log('------unmounted------');
},
activated () {
console.log('------activated------');
},
deactivated () {
console.log('------deactivated------');
},
errorCaptured() {
console.log('------errorCaptured------');
},
}).mount("#app")
下面将详细介绍组件的生命周期内各个方法的含义和用法。
beforeCreate 和 created
beforeCreate 方法
这个阶段在实例初始化之后,数据观测(Data Observer)和 event/watcher
事件配置之前被调用。需要注意的是,这个阶段无法获取到 Vue
组件 data
中定义的数据,官方也不推荐在这里操作 data
,如果确实需要获取 data
,可以从 this.$options.data()
获取。
created 方法
在 beforeCreate
执行完成之后,Vue
会执行一些数据观测和 event/watcher
事件的初始化工作,将数据和 data
属性进行绑定以及对 props
、methods
、watch
等进行初始化,另外还要初始化一些 inject
和 provide
。
可以从图3-2来了解 beforeCreate
方法和 created
方法的主要流程与执行逻辑。

从图3-2可以得知,在 created
方法执行时,template
也是一个关键设置,如果当前 Vue
组件设置了 template
属性,则将其作为模板编译成 render
函数,即 template
中的 HTML 内容会渲染到 el
节点内部。如果当前 Vue
组件没有设置 template
属性,则将当前 el
节点所在的 HTML
元素(即 el.outerHTML
)作为模板进行编译。因此,如果组件没有设置 template
属性,就相当于设置了内容是 el
节点的 template
。需要注意的是,这个阶段并没有真正地把 template
或者 el
渲染到页面上,只是先将内容准备好(即把 render
函数准备好)。
在使用 created
钩子函数时,通常执行一些组件的初始化操作或者定义一些变量,如果是一个表格组件,那么在 created
时就可以调用 API
接口,开始发送请求来获取表格数据等。
在组合 |
beforeMount 和 mounted
beforeMount 方法
前文提到了 el
和 template
属性以及 render
函数,render
函数用于给当前 Vue
实例挂载 DOM
(Vue
组件渲染 HTML
内容),这里的 beforeMount
就是渲染前要执行的程序逻辑。
mounted 方法
这个阶段开始真正地执行 render
方法进行渲染,之前设置的 el
会被 render
函数执行的结果所替换,也就是说将结果真正渲染到当前 Vue
实例的 el
节点上,这时就会调用 mounted
方法。从图3-3可以看到 beforeMount
方法和 mounted
方法的主要流程与执行逻辑。

mounted
这个钩子函数的使用频率非常高,当触发这个函数时,就代表组件的用户界面已经渲染完成,可以在 DOM
中获取这个节点。通常用这个方法执行一些用户界面节点获取的操作,例如在 Vue
中使用一个 jQuery
插件,在这个方法中就可以获得插件所依赖的 DOM
,从而进行初始化。
但是需要注意的是,mounted
不会保证所有的子组件也都一起被挂载。如果读者希望等到整个视图都渲染完毕,可以在 mounted
内部使用 this.$nextTick
,代码如下:
mounted() {
this.$nextTick(function () {
// 仅在渲染整个视图之后运行的代码
})
}
beforeUpdate 和 updated
前面讲解的生命周期函数在调用 Vue.createApp({})
和 mount(el)
方法时就会触发,我们可以把它们归类成实例初始化时自动调用的钩子函数,而 beforeUpdate
和 updated
这两个方法若要触发,则需要特定的场景。
updated 方法
当执行完 beforeUpdate
方法后,就会触发当前组件挂载 DOM
内容的修改,当前 DOM
修改完成后,便会触发 updated
方法,在 updated
方法中可以获取更新之后的 DOM
。
下面用代码来模拟 beforeUpdate
和 updated
的触发时机,如示例代码3-1-1所示。
<div id="app">
{{message}}
<button @click="clickCallback">点击</button>
</div>
const vm = Vue.createApp({
data() {
return {
message: 'I am Tom'
}
},
beforeCreate() {
console.log('------beforeCreate------');
},
created() {
console.log('------created------');
},
beforeMount() {
console.log('------beforeMount------');
},
mounted() {
console.log('------mounted------');
},
beforeUpdate () {
console.log('------beforeUpdate------');
},
updated () {
console.log('------updated------');
},
methods:{
clickCallback: function(){
this.message = 'I am Jack'
}
}
}).mount('#app')
运行这段代码后,会依次看到 beforeCreate
、created
、beforeMount
和 mounted
方法打印在 Chrome 浏览器的控制台上;单击按钮,会看到文字由 “I am Tom” 变成了 “I am Jack”;然后在控制台上可以看到依次打印了 beforeUpdate
和 updated
,如图3-4所示。

由此可知,这两个方法是可以触发或者执行多次的,所以在 Vue
组件的生命周期中,每当 data
中的值被修改都会执行这两个方法。执行流程如图3-5所示。

前面讲解了双向绑定中 Model
影响 DOM
的具体体现,在 MVVM
模式的 Model
,也就是 Vue
中 data
的值发生改变时,会触发 beforeUpdate
和 updated
这两个方法。下面就来演示双向绑定中 DOM
影响 Model
的具体体现,同样也会触发这两个方法,如示例代码3-1-2所示。
<div id="app">
<input type="text" v-model="message">
</div>
const vm = Vue.createApp({
data() {
return {
message: 'I am Tom'
}
},
beforeUpdate () {
console.log(this.message)
console.log('------beforeUpdate------');
},
updated () {
console.log(this.message)
console.log('------updated------');
}
}).mount('#app')
在上面的代码中,使用了 <input>
标签,并给 <input>
设置了 v-model
指令,表示与 data
中的 message
进行关联,这时修改 <input>
中的内容就是修改了 DOM
的内容,可以在控制台中看到会触发 beforeUpdate
和 updated
这两个方法,同时 message
的值也在实时变动,这就是双向绑定中 DOM
影响 Model
的具体体现。Chrome 浏览器的控制台如图3-6所示。

v-model
指令不仅可以作用在 <input>
上,还可以作用在自定义组件上,我们会在后面讲解。
beforeUnmount 和 unmounted
组件卸载,正如万物有生有灭一样,既然组件有创建,也就必然有消亡。如果频繁调用创建的代码,但是一直没有清除,就会造成内存飙升,而且一直不释放,还有可能导致 “内存泄漏” 问题,这也是卸载组件的意义。
unmounted 方法
unmounted
方法在 Vue
组件卸载后调用。调用后,Vue
组件关联的所有事件监听器都会被移除,所有的当前组件的子组件也会被销毁。
在触发卸载操作之后,首先会将当前组件从其父组件中清除,然后清除当前组件的事件监听和数据绑定,清除一个 Vue
组件可以简单理解为将 Vue
对象关联的一些数据类型的变量清空或者置为 null
。
一般来说,卸载组件常发生在采用 v-if
指令进行逻辑判断时,如示例代码3-1-3所示。
<div id="app">
<button @click="flag = 2">点我</button>
<componenta v-if="flag == 1"></componenta>
<componentb v-else></componentb>
</div>
const componenta = {
template: '<h2>myComponent a</h2>',
beforeUnmount(){
console.log('------componenta:beforeUnmount------');
},
unmounted(){
console.log('------componenta:unmounted------');
}
}
const componentb = {
template: '<h2>myComponent b</h2>',
beforeMount(){
console.log('------componentb:beforeMount------');
},
mounted(){
console.log('------componentb:mounted------');
}
}
const app = Vue.createApp({
data(){
return {
flag: 1
}
},
components: {
componenta:componenta,
componentb:componentb
}
}).mount('#app')
当我们单击按钮时,flag
值被设置为 2
,这就触发了 v-if
指令的逻辑,<componenta>
组件卸载,<componentb>
组件挂载,控制台打印的日志如图3-7所示。

可以看到 <componenta>
组件卸载时,触发了 beforeUnmount
和 unmounted
,<componentb>
组件挂载时触发了 beforeMount
和 mounted
。如果想要主动触发对根组件的卸载,可以调用根实例的 unmount()
方法,调用之后就会清理与其他实例的联系,解绑它的全部指令及事件监听器。
errorCaptured
该方法表示当捕获一个来自当前子孙组件的错误时被触发,注意当前组件报错不会触发。这里的报错一般只会限制在当前 Vue
根实例下代码所抛出的 DOMException
或者异常 Error
对象(new Error()
)等错误,如果是 Vue
之外的代码,是不会触发的。该方法会收到三个参数:错误对象、发生错误的组件实例 以及 一个包含错误来源信息的字符串。在某个子孙组件的 errorCaptured
返回 false
时,可以阻止该错误继续向上传播。
另外,也可以在全局配置 errorCaptured
,这样就可以监听到所有属于该根实例的报错信息,配置如示例代码3-1-4所示。
const app = Vue.createApp({
mounted(){
throw new Error('err')
}
})
app.config.errorHandler = (err, vm, info) => {
console.error(err)
console.log(vm)
// 'info' 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
console.log(info)
}
app.mount('#app')
在浏览器控制台查看打印的报错信息,如图3-8所示。

如果某个子孙组件 errorCaptured
方法返回 false
以阻止错误继续向上传播,那么它会阻止其他任何会被这个错误唤起的 errorCaptured
方法和全局的 config.errorHandler
的触发。
错误传递规则:
-
默认情况下,所有的错误都会被发送到应用级的
app.config.errorHandler
(前提是这个函数已经定义),这样这些错误都能在一个统一的地方报告给分析服务。 -
如果组件的继承链或组件链上存在多个
errorCaptured
钩子,对于同一个错误,这些钩子会被按从底至上的顺序一一调用。这个过程被称为 “向上传递”,类似于原生DOM
事件的冒泡机制。 -
如果
errorCaptured
钩子本身抛出了一个错误,那么这个错误和原来捕获到的错误都将被发送到app.config.errorHandler
。 -
errorCaptured
钩子可以通过返回false
来阻止错误继续向上传递。即表示 “这个错误已经被处理了,应当被忽略”,它将阻止其他的errorCaptured
钩子或app.config.errorHandler
因这个错误而被调用。
activated 和 deactivated
keep-alive
的作用:
<keep-alive>
是一个内置组件,用于缓存包裹的组件。这样,当切换组件时,组件不会被销毁,而是会被保留在内存中并等待重新激活。因此,activated
和 deactivated
钩子只在被 <keep-alive>
包裹的组件中才会触发。
-
activated
和deactivated
只对被<keep-alive>
缓存的组件有效,如果组件没有被<keep-alive>
包裹,这两个钩子不会被触发。 -
这两个钩子不会在每次视图切换时都被调用,而是只会在组件状态改变时触发。
这两个方法并不是标准的 Vue
组件的生命周期方法,它们的触发时机需要结合 vue-router
及组件 keep-alive
来使用。
当你在 Vue
应用中使用 vue-router
进行页面路由切换时,若将一些组件用 <keep-alive>
包裹,它们将被缓存并且不会在页面切换时被销毁。这样可以提高性能,避免不必要的重渲染。此时,activated
和 deactivated
钩子与 <keep-alive>
配合使用,触发时机是:
-
activated
:当<keep-alive>
包裹的组件恢复显示时,意味着组件在路由切换时被激活,从缓存中恢复并显示时触发。 -
deactivated
:当<keep-alive>
包裹的组件不再显示时,组件被缓存起来,停用时触发。
这两个钩子特别有助于在路由切换时管理组件的状态,比如重新发起请求、清除定时器等。
renderTracked 和 renderTriggered
renderTracked
和 renderTriggered
是 Vue 3
中与响应式系统相关的调试工具,它们是 Vue 3
响应式系统内部用于追踪渲染过程时变化的两个钩子函数,通常用于开发和调试阶段。它们并不是用户直接使用的 API
,而是 Vue
的调试工具,帮助开发者在开发过程中更好地理解组件的更新机制。
renderTracked
renderTracked
钩子函数在 响应式属性被访问并用于组件渲染时 被调用。它通常在 渲染过程 中触发,指示某个响应式数据被 追踪(即依赖收集)。
你可以在组件渲染期间监听哪些响应式属性被访问,并在 renderTracked
中进行处理。以下是 renderTracked
的用法示例:
import { createApp, reactive, effect } from 'vue';
import { renderTracked } from 'vue';
const app = createApp({
setup() {
// 创建一个响应式数据对象
const state = reactive({
count: 0,
name: 'Vue'
});
// 使用 renderTracked 来监听依赖追踪
renderTracked((e) => {
// e 是一个事件对象,包含了相关的追踪信息
console.log('Tracked Dependency:', e);
});
// 创建一个 effect 来观察 state 数据
effect(() => {
console.log(state.count); // 访问了 state.count,触发依赖追踪
});
// 修改响应式数据
state.count++; // 这将触发 renderTracked 和 effect
return { state };
}
});
app.mount('#app');
renderTracked
监听的事件对象:
当你访问某个响应式数据的 getter
时,renderTracked
会被触发,并且它的回调函数会接收到一个事件对象,该对象包含了关于依赖追踪的详细信息。具体来说,它包含以下字段:
-
effect
:表示当前的effect
(响应式副作用)对象。 -
target
:表示当前的响应式对象(例如state
)。 -
key
:表示被访问的响应式数据的属性名(例如count
)。 -
type
:表示属性的类型,可能是get
、set
、has
等。
如何在开发中使用 renderTracked
:
-
性能优化:使用
renderTracked
来观察哪些响应式数据被访问,帮助你找出组件的重新渲染触发点。通过这个工具,你可以知道哪些数据实际上对当前的渲染产生了影响。 -
调试:如果你在开发过程中遇到渲染过于频繁或者数据依赖错误的问题,
renderTracked
可以帮助你检查哪些数据触发了更新。你可以通过console.log
输出依赖关系来诊断问题。 -
理解
Vue
响应式系统:renderTracked
是理解Vue 3
响应式系统内部工作原理的好工具,帮助你深入了解如何进行依赖追踪。
renderTriggered
renderTriggered
是 Vue 3
中的一个开发工具钩子,它用于追踪组件的重新渲染过程。当组件的响应式数据发生变化,并导致视图重新渲染时,renderTriggered
钩子会被触发。这个钩子对于调试和优化性能特别有用,尤其是在开发过程中帮助我们理解哪些响应式数据的变化触发了组件的重新渲染。
Vue 3
中的响应式系统依赖于一个 响应式对象,当你修改对象中的某些属性时,Vue 会通过 依赖追踪 来追踪哪些组件或计算属性依赖于这个数据。然后,当这个数据发生变化时,Vue
会自动重新渲染这些组件。
renderTriggered
就是当这些重新渲染发生时,Vue
会触发一个事件,告诉我们哪些数据的变化导致了视图的更新。
renderTriggered
用法:
renderTriggered
是 Vue 3
的一个生命周期钩子函数,用于在组件渲染时触发。你可以使用它来观察组件视图的更新以及触发重新渲染的具体原因。
import { reactive, effect, renderTriggered } from 'vue';
const state = reactive({
count: 0
});
effect(() => {
console.log(state.count); // 当 state.count 变化时,重新渲染并触发 effect
});
renderTriggered(() => {
console.log("Component re-rendered due to data change.");
});
state.count++; // 触发视图更新,renderTriggered 会被调用
如何在开发中使用 renderTriggered
:
-
调试性能:当组件频繁重新渲染时,
renderTriggered
可以帮助你识别哪些数据变动导致了不必要的重渲染。你可以通过这个钩子来确定是否需要优化某些计算属性或方法,以减少不必要的渲染。 -
性能优化:通过结合
renderTriggered
和renderTracked
,你可以深入了解组件的渲染过程,优化响应式数据的管理,减少组件的不必要重渲染。