8.1 生命周期概述
Vue组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子。

-
规律:
生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。
-
Vue2的生命周期创建阶段:
beforeCreate、created挂载阶段:
beforeMount、mounted更新阶段:
beforeUpdate、updated销毁阶段:
beforeDestroy、destroyed -
Vue3的生命周期创建阶段:
setup挂载阶段:
onBeforeMount、onMounted更新阶段:
onBeforeUpdate、onUpdated卸载阶段:
onBeforeUnmount、onUnmounted -
常用的钩子:
onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)
8.2 创建阶段
1、Vue2(beforeCreate、created)
vue2在创建阶段有两个生命周期钩子,分别是beforeCreate和created,一个是创建前执行一个是创建完成执行。这两个钩子最大的区别体现在数据是否初始化完成,以下面为例:
javascript
export default {
data() {
return {
sum: 1
}
},
beforeCreate() {
console.log('创建前');
console.log(this.sum);
},
created() {
console.log('创建完毕');
console.log(this.sum);
}
}

beforeCreate钩子执行时,data、methods、watch、computed 等都还没有被初始化,所以你访问不到任何数据,也调用不了 watch。
created钩子执行时, data、methods、watch、computed 都已经初始化完成,所以你能访问到数据。
关于watch补充说明,虽然watch在created钩子已经初始化完成,但它的回调函数默认不会在此时执行,它只是被"准备好了",等待数据变化时才触发。默认情况(immediate: false):watch的回调函数不会在组件创建时执行,只有当你监听的数据发生变化时才会触发。设置了 immediate: true:watch的回调函数会在 created钩子之后立即执行一次。
2、Vue3(setup)
vue3没有细分创建前和创建完成这两个阶段,它统一在setup钩子里完成。它不仅具备了Vue2的beforeCreate和created这两个钩子的作用,而且它可以把所有业务逻辑都直接写在 setup 里,包括数据请求、事件监听、计算属性、副作用管理等。
关于setup有以下两种写法:
(1)普通setup()
xml
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => count.value++
// ❌ 如果没有 return,模板就无法使用 count 和 increment
return {
count,
increment
}
}
}
</script>
(2)<script setup>(不需要 return)
xml
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
// ✅ 无需 return,模板可以直接使用 count 和 increment
</script>
目前基本上都使用<script setup>这种语法糖,在这个模式下,所有顶层定义的变量和函数,都会被自动暴露给模板,完全不需要手动return。
当然,不管是vue2的beforeCreate/created还是vue3的setup,此时DOM元素还没有渲染出来,如果需要操作DOM,依然要在挂载阶段进行操作。
8.3 挂载阶段
挂载阶段分为挂载前和挂载结束两个阶段,分别对应vue2的beforeMount/mounted与vue3的onBeforeMount/onMounted,它们除了命名不同其它基本一致。
1、挂载前
在绝大多数日常业务开发中,这个钩子确实很少被用到,甚至可以说它对于90%的开发者来说,确实是"没什么用"的,因为在这个钩子能做的事,基本上都可以在created钩子做到。在这里对created与beforeMount做一个简单的区别:
created:data、methods都已经准备好,但页面模板还没有被编译成DOM元素 。此时你无法操作 this.$el,也无法通过 document.querySelector 找到页面上的任何元素(除非是静态硬编码的)。
beforeMount:模板编译已经完成,虚拟 DOM 节点已经生成 ,但还没有被挂载(渲染)到真实的 DOM 中 。此时 this.$el 仍然指向旧的挂载点(或空),真实 DOM 还是不可用。
使用场景:如果你能在虚拟 DOM 被渲染成真实 DOM 之前,对它做最后一次修改 ,就能避免不必要的 DOM 重绘。在编写 Vue 的自定义指令 或服务端渲染(SSR) 时,beforeMount 是区分客户端(浏览器)和服务器端执行逻辑的关键节点。因为 SSR 只会执行到 beforeMount 为止,后续的 mounted 只会在客户端执行。
2、挂载结束
mounted钩子可以说是 Vue 生命周期中最核心、最常用 的钩子之一。它的定位非常明确:当组件被挂载到真实 DOM 之后,你可以安全地操作 DOM 了。
| 用途分类 | 具体场景 | 为什么必须在 mounted 中做? |
|---|---|---|
| 1. 操作 DOM 元素 | 获取元素宽高、滚动位置、聚焦输入框、动态修改样式等 | 只有挂载后,this.$el 和 this.$refs 才指向真实的 DOM 节点。 |
| 2. 初始化第三方 DOM 库 | 初始化 ECharts 图表、地图(如高德/百度)、swiper 轮播、富文本编辑器等 |
这些库都需要一个真实的 DOM 容器来渲染,必须等到挂载完成。 |
| 3. 启动定时器或轮询 | 定时刷新数据、倒计时、轮询接口状态等 | 通常与 DOM 展示或用户交互相关,挂载后启动更合理。 |
| 4. 依赖 DOM 的数据请求 | 需要获取页面元素尺寸后再请求数据(如自适应布局) | 数据请求本身可在 created 做,但若依赖 DOM 信息,则必须在 mounted。 |
| 5. 设置事件监听 | 监听 window 的 resize、scroll 事件,或全局键盘事件等 |
需要确保组件已存在,避免在销毁时残留监听(需配合 beforeDestroy 清理)。 |
注意事项:
(1) 避免在 mounted 中修改响应式数据 :如果在 mounted 里直接修改了 data 中的某个值,会触发组件重新渲染,可能会引起性能问题或视觉闪烁。如果确实需要改,可以用 this.$nextTick 推迟到下一次 DOM 更新后执行。
(2) 子组件的挂载顺序 :父组件的 mounted 会先于 子组件的 mounted 执行。如果父组件需要等待子组件完全挂载后再操作,可以使用 this.$nextTick 或监听子组件的 mounted 事件。
(3) SSR(服务端渲染)中不会执行 :如果你的项目使用了 SSR(如 Nuxt),mounted 钩子只在浏览器端执行 ,服务器端不会调用。因此,任何浏览器特有的 API(如 window、document)操作都必须放在 mounted 中。
8.4 更新阶段
updated钩子在响应式数据发生变化,导致组件的虚拟 DOM 重新构建并完成对比(patch)之后 被调用。简单来说,就是:数据变了 → 组件重新渲染了 → DOM 更新完了 → 这时updated钩子执行。
1、主要用途
updated的核心价值在于:当你需要在 DOM 更新完成后执行某些操作时,它是一个精确的时机。典型场景包括:
| 场景 | 说明 |
|---|---|
| 1. 操作更新后的 DOM | 例如,数据变化后,需要获取某个元素的新尺寸、新滚动位置,或对 DOM 做额外的操作。 |
| 2. 更新第三方 DOM 库 | 例如,ECharts 图表数据变化后,需要重新设置 setOption;地图需要重新调整视图等。 |
| 3. 触发子组件或外部依赖的更新 | 例如,子组件内部依赖父组件传入的 prop,当 prop 变化后,需要在 updated 中做后续处理。 |
| 4. 调试或日志记录 | 在开发环境下,可以在 updated 中打印当前状态,观察组件渲染情况。 |
2、实战代码示例
xml
<template>
<div>
<ul ref="listRef">
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
<p>列表长度: {{ list.length }}</p>
<button @click="addItem">添加一项</button>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' }
]
}
},
methods: {
addItem() {
this.list.push({ id: Date.now(), name: 'New Item' })
}
},
updated() {
// ✅ 1. 获取更新后的 DOM 信息(比如列表高度变化)
const height = this.$refs.listRef.offsetHeight
console.log('列表更新后高度:', height)
// ✅ 2. 如果列表滚动到底部,自动滚动到最新项(消息列表常见场景)
const container = this.$refs.listRef
container.scrollTop = container.scrollHeight
// ✅ 3. 触发外部依赖(比如通知父组件或更新第三方库)
// this.updateExternalChart()
// ⚠️ 4. 注意:不要在这里修改响应式数据,会导致无限循环
// this.list = [...] // ❌ 千万不要这样做!
}
}
</script>
3、注意事项
不要在 updated 中修改响应式数据!
如果在updated中修改了data中的某个值,会立即触发新一轮的渲染,然后再次进入updated,从而形成死循环,最终导致浏览器崩溃。
javascript
updated() {
// ❌ 绝对禁止:修改响应式数据
this.count++ // 会触发重新渲染 → 再次进入 updated → 无限循环
}
如果确实需要在 DOM 更新后修改数据,可以使用 this.$nextTick 来推迟修改,但在updated中直接做依然风险很高,一般不推荐。
4、updated vs watch
| 对比维度 | watch |
updated |
|---|---|---|
| 触发粒度 | 精确到某个具体数据(监听谁就触发谁) | 粗粒度(任何数据变化都会触发) |
| 执行时机 | 数据变化时立即执行(DOM 可能还没更新) | DOM 更新完成后执行 |
| 是否包含 DOM 操作 | 可以,但需要配合 this.$nextTick 才能拿到新 DOM |
直接可以操作最新 DOM |
| 适用场景 | 监听特定数据变化,执行特定逻辑 | 需要等待 DOM 更新后做通用处理 |
-
如果你只关心某个具体数据的变化 → 用
watch -
如果你关心 DOM 是否更新完毕,且不关心是哪个数据触发的 → 用
updated -
如果你需要同时监听数据变化并在 DOM 更新后做操作 → 用
watch+this.$nextTick
8.5 销毁阶段
1、清理工作
销毁阶段最重要的任务,用一个词概括就是:清理。
| 清理类型 | 具体内容 | 为什么要清理? |
|---|---|---|
| 清除定时器 | clearInterval() / clearTimeout() |
定时器会持续占用内存和 CPU,即使组件已销毁,它仍在后台运行。 |
| 移除全局事件监听 | removeEventListener() |
挂载在 window、document 或 EventBus 上的监听器不会自动销毁,会一直存在,导致内存泄漏或逻辑错乱。 |
| 销毁第三方库实例 | ECharts 的 dispose()、地图的 destroy()、swiper 的 destroy() 等 |
第三方库通常会在 DOM 上绑定大量事件或创建复杂的对象,需要手动释放资源。 |
| 取消未完成的请求 | 取消 axios 请求(CancelToken)或 fetch 请求(AbortController) |
组件销毁后,返回的数据已经无用,但请求本身仍会占用网络和内存资源。 |
| 断开 WebSocket / 实时连接 | socket.close() |
保持无效连接会浪费服务器资源和网络带宽。 |
| 取消观察 / 订阅 | 取消对 watch 的监听(unwatch)、取消对 EventBus 或 store 的订阅 |
防止组件销毁后,仍然响应全局事件或状态变化,导致报错或逻辑错乱。 |
如果忘记清理上述资源,有可能会发生以下情况。
-
定时器泄漏:用户从页面 A 跳到页面 B,但页面 A 的定时器还在运行,每隔几秒还在请求数据、更新已经不存在的 DOM,CPU 和内存持续消耗。
-
事件监听泄漏 :
window.resize事件仍然在调用一个已销毁组件的方法,导致报错 "Cannot read property of undefined"。 -
第三方库泄漏:ECharts 实例没有被销毁,它持有的 DOM 引用和事件监听会阻止垃圾回收,导致内存不断攀升。
-
请求泄漏:组件销毁后,异步请求返回的数据试图更新一个已经不存在的组件状态,导致控制台报错。
2、beforeDestroy vs destroyed
实际开发中,你几乎只需要关心 beforeDestroy(Vue 3 中为 onBeforeUnmount)。destroyed 很少被用到,因为此时组件已经"死亡",能做的事情非常有限。
| 对比维度 | beforeDestroy |
destroyed |
|---|---|---|
| 触发时机 | 销毁前 | 销毁后 |
data / methods 是否可用 |
✅ 完全可用 | ❌ 已不可用 |
| DOM 是否存在 | ✅ 存在 | ❌ 已移除 |
| 适合做什么 | 99% 的清理工作(定时器、事件、第三方库) | 极少数收尾工作(通常不需要) |