Vue3的生命周期钩子函数都有啥 具体怎么用
刚上手Vue3的开发者,十有八九会绕不开生命周期钩子函数的疑问:原来Vue2那套beforeCreate、created好像没怎么在setup里看见?onMounted和Vue2的mounted是一回事吗?还有新增的那些钩子都是干啥的?别着急,咱们今天就把这些问题拆得明明白白,连每个钩子的真实使用场景都给你搭好例子,看完就能在项目里用上。
Vue2到Vue3的钩子映射先搞懂?不完全是,但可以当入门参考
很多人学新东西喜欢找旧框架的对照,这个思路没问题,但得注意Vue3的核心逻辑变了——从Options API(选项式)转向了Composition API(组合式),虽然底层的生命周期阶段没变,但钩子的命名、调用方式、触发时机的细节都有调整,咱们先列个对照清单打底,别记混了: Options API的beforeCreate和created,在Vue3里直接用setup函数替代就行,因为setup会在组件实例创建之前执行?不对不对,得精准点:setup在组件实例被初始化之后、响应式数据和props被处理之前就开始跑了?等下,捋清楚阶段顺序更重要。
咱们直接讲Vue3的真实生命周期阶段,从组合式API和选项式API两个方向来,因为现在很多项目还是混合用的,两边都得会。
第一阶段:组件创建和初始化阶段
这个阶段主要是Vue在后台给组件“搭架子”,比如准备好响应式系统、解析props、拿到template模板之类的,不管是组合式还是选项式,这个阶段能让你操作的钩子/代码都不多,但很关键。 组合式API里,这个阶段主要就是setup函数本身——没有对应的beforeCreate/created钩子了,因为setup刚好覆盖了这两个钩子的所有场景,甚至更强大,比如你在Vue2里created钩子做的“请求接口拿初始数据”“注册全局事件监听”“根据props初始化本地响应式数据”,现在都可以直接写在setup里。 举个具体的例子:比如做一个用户详情页,需要通过props传进来的userId去请求接口拿用户信息,在Vue2里,你可能会这么写:
export default {
props: { userId: Number },
data() { return { userInfo: null } },
created() { this.fetchUserInfo() },
methods: { fetchUserInfo() { /* 调用axios之类的 */ } }
}
到了Vue3的组合式API里,就可以简化成:
import { ref, onMounted } from 'vue' // 这里先别急,onMounted后面讲
import { getUserById } from '@/api/user'
export default {
props: { userId: Number },
setup(props) {
const userInfo = ref(null)
const fetchUserInfo = async () => {
try {
const res = await getUserById(props.userId)
userInfo.value = res.data
} catch (err) {
console.error('获取用户信息失败', err)
}
}
// 这里其实可以直接写fetchUserInfo(),但后面会说为什么有时候要等onMounted
fetchUserInfo()
return { userInfo, fetchUserInfo }
}
}
不过这里要提醒一句:setup里不能用this,因为组件实例还没完全创建好(对,响应式数据和props处理之前就只有setup的上下文),所以要访问props的话,必须通过setup的第一个参数拿到,访问attrs/slots/emit的话,通过第二个参数,比如const { emit } = defineProps(['userId'])?哦不对,defineProps和defineEmits是Vue3.2+的script setup语法糖,比普通setup更爽,后面单独提一下,这里先按标准setup说。
选项式API里,这个阶段还是保留了beforeCreate和created,但不建议在新项目里用,除非是维护老代码混合迁移,触发顺序和Vue2一样:beforeCreate先跑(此时data、methods、props还没解析,用不了),created后跑(此时data、methods、props都能用了,但DOM还没挂载,不能操作DOM)。
第二阶段:DOM挂载阶段
这个阶段是组件从“后台代码”变成“用户能看到的界面”的关键节点,钩子最多,也是大家用得最频繁的阶段,不管组合式还是选项式,这个阶段都有对应的钩子,而且功能差不多,但触发时机的细节组合式API要注意一点:组合式的钩子会比选项式的同阶段钩子晚一点触发,如果混合用的话,得按这个顺序来规划代码。
组合式API的DOM挂载钩子
组合式API里,DOM挂载阶段有两个钩子:onBeforeMount和onMounted,都需要从vue里导入才能用。
- onBeforeMount:这个钩子在Vue把解析好的DOM节点挂载到页面之前触发,此时页面上还找不到对应的DOM元素,但响应式数据已经处理好了,你可以在这里做一些“修改DOM渲染配置”的操作,比如给某个元素添加自定义的data-*属性(不过这个也可以用Vue的指令实现,更推荐用v-bind),或者修改ref引用的虚拟DOM的props?虚拟DOM的话可能有点绕,新手先记住:这个钩子用得很少,除非你有非常特殊的需求。
- onMounted:这个钩子是整个Vue3生命周期里用得最多的钩子之一,在DOM节点完全挂载到页面并且所有子组件也都挂载完成之后触发,此时你可以:
- 操作真实DOM:比如用ref获取到某个div的引用,然后给它绑定原生的鼠标移动事件、获取它的宽高之类的。
- 发起需要依赖DOM的请求:比如请求接口拿图片列表,然后计算图片容器的宽度来做瀑布流布局——因为瀑布流布局需要知道容器的宽度才能计算列数,所以必须等DOM挂载好之后再请求并布局。
- 初始化第三方库:比如引入ECharts图表库,必须等DOM挂载好之后才能找到图表容器的DOM节点,然后初始化图表实例。
刚才那个用户详情页的例子,为什么有时候要把fetchUserInfo放在onMounted里?比如如果用户详情页的某个数据展示需要依赖DOM的宽高(比如展示一个根据容器宽度自适应的折线图),那必须等DOM挂载好之后再请求数据,然后渲染图表,如果只是单纯的文字展示,放在setup里直接调用也没问题,还能更早拿到数据。
再举个初始化ECharts的例子,用script setup语法糖更简洁:
<template>
<div ref="chartRef" style="width: 100%; height: 400px;"></div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue' // onBeforeUnmount后面讲
import * as echarts from 'echarts'
const chartRef = ref(null) // ref引用,用来获取DOM节点
let chartInstance = null // 全局图表实例,用来销毁
// 初始化图表的函数
const initChart = () => {
if (!chartRef.value) return
chartInstance = echarts.init(chartRef.value)
chartInstance.setOption({ { text: '用户近30天活跃度' },
xAxis: { type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
yAxis: { type: 'value' },
series: [{ type: 'line', data: [120, 200, 150, 80, 70, 110, 130] }]
})
}
// 页面挂载完成后初始化图表
onMounted(() => {
initChart()
// 监听窗口大小变化,重新调整图表大小
window.addEventListener('resize', handleResize)
})
// 窗口大小变化的处理函数
const handleResize = () => {
chartInstance?.resize()
}
// 页面卸载前销毁图表实例和事件监听,防止内存泄漏
onBeforeUnmount(() => {
chartInstance?.dispose()
window.removeEventListener('resize', handleResize)
})
</script>
选项式API的DOM挂载钩子
选项式API里,这个阶段还是保留了beforeMount和mounted,触发顺序和组合式的一样,但新手记组合式的就行,因为新项目主流都是用组合式的,这里只提一句:选项式的mounted里可以用this.$refs获取DOM节点,和Vue2一样。
第三阶段:组件更新阶段
这个阶段是当组件的响应式数据发生变化,或者父组件传递的props发生变化,导致Vue重新渲染页面时触发的,也是大家容易踩坑的阶段——千万不要在更新阶段的钩子函数里直接修改导致更新的那个响应式数据,否则会造成无限循环更新!
组合式API的组件更新钩子
组合式API里,组件更新阶段有两个钩子:onBeforeUpdate和onUpdated,同样需要从vue里导入。
- onBeforeUpdate:这个钩子在Vue开始重新渲染页面之前触发,此时响应式数据已经变了,但页面上的DOM还是旧的,你可以在这里做一些“保存旧DOM状态”的操作,比如保存某个滚动条的位置——等下,保存滚动条位置其实可以用keep-alive组件的钩子,后面单独提keep-alive相关的,这里新手先记住:这个钩子用得也不多,除非你有特殊需求。
- onUpdated:这个钩子在Vue重新渲染页面并且所有DOM更新完成之后触发,此时响应式数据和页面上的DOM都是最新的,你可以在这里做一些“需要依赖最新DOM”的操作,比如刚才ECharts的例子,如果数据是异步更新的,那更新数据之后需要重新调用chartInstance.setOption,不过这个可以直接在数据更新的函数里调用,不一定非要放在onUpdated里——除非你不知道数据什么时候会更新(比如监听了一个第三方库的数据变化),才需要放在onUpdated里。
举个保存滚动条位置的例子(不用keep-alive的极端情况):比如做一个无限滚动列表,当用户加载新数据的时候,滚动条会自动跳到顶部,你可以在onBeforeUpdate里保存滚动条的位置,在onUpdated里恢复:
<template>
<div ref="listRef" style="height: 600px; overflow-y: auto;">
<div v-for="item in list" :key="item.id" style="height: 100px; border-bottom: 1px solid #eee;">{{ item.text }}</div>
</div>
<button @click="loadMore">加载更多</button>
</template>
<script setup>
import { ref, onBeforeUpdate, onUpdated } from 'vue'
const listRef = ref(null)
const list = ref([
{ id: 1, text: '第一条数据' },
{ id: 2, text: '第二条数据' },
{ id: 3, text: '第三条数据' },
{ id: 4, text: '第四条数据' },
{ id: 5, text: '第五条数据' }
])
let scrollTop = 0 // 保存旧的滚动条位置
const loadMore = () => {
// 模拟加载新数据
const newList = []
for (let i = 0; i < 5; i++) {
newList.push({ id: list.value.length + 1 + i, text: `第${list.value.length + 1 + i}条数据` })
}
list.value = [...list.value, ...newList]
}
// 更新前保存滚动条位置
onBeforeUpdate(() => {
if (listRef.value) {
scrollTop = listRef.value.scrollTop
}
})
// 更新后恢复滚动条位置
onUpdated(() => {
if (listRef.value) {
listRef.value.scrollTop = scrollTop
}
})
</script>
不过这个例子其实不太好,因为无限滚动列表加载新数据的时候,滚动条跳到顶部是因为列表高度变化了?不对,刚才的例子里是用div的overflow-y: auto,加载新数据后div的内容变高了,但滚动条的位置应该是不变的?哦对,我刚才搞错了,应该是当你切换到另一个页面再切回来的时候,如果不用keep-alive,列表会重新渲染,滚动条会跳到顶部,这时候用keep-alive的钩子更合适,后面讲。
选项式API的组件更新钩子
选项式API里,这个阶段保留了beforeUpdate和updated,和Vue2一样,触发顺序也一样,同样要注意不要无限循环更新。
第四阶段:组件卸载阶段
这个阶段是当组件从页面上被移除时触发的,最重要的作用是清理资源,防止内存泄漏——这点非常重要,如果你在onMounted里注册了全局事件监听、定时器、WebSocket连接、第三方库实例,一定要在这个阶段销毁掉,否则会占用浏览器的内存,导致页面越来越卡。
组合式API的组件卸载钩子
组合式API里,组件卸载阶段有两个钩子:onBeforeUnmount和onUnmounted,同样需要导入。
- onBeforeUnmount:这个钩子在组件开始从页面上移除之前触发,此时组件实例还是完全可用的,响应式数据、方法、DOM节点都还在,是清理资源的最佳时机,刚才ECharts的例子里,我们就是在这里销毁了图表实例和事件监听。
- onUnmounted:这个钩子在组件完全从页面上移除之后触发,此时组件实例已经被销毁了,响应式数据、方法、DOM节点都用不了了,用得很少,新手可以暂时不用管。
再举个清理定时器的例子:比如做一个倒计时组件,当组件被移除的时候,一定要把定时器关掉:
<template>
<div>倒计时:{{ count }}</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const count = ref(10)
let timer = null
const startCountdown = () => {
timer = setInterval(() => {
if (count.value > 0) {
count.value--
} else {
clearInterval(timer)
}
}, 1000)
}
onMounted(() => {
startCountdown()
})
onBeforeUnmount(() => {
// 双重保险,防止倒计时结束后还没卸载组件,或者提前卸载组件
clearInterval(timer)
})
</script>
选项式API的组件卸载钩子
选项式API里,这个阶段保留了beforeUnmount和unmounted,原来Vue2里的beforeDestroy和destroyed改了名字,功能一样,触发顺序也一样,清理资源也是在这里做。
第五阶段:特殊钩子函数(keep-alive相关、错误处理相关)
除了上面的四个核心阶段,Vue3还有几个特殊的钩子函数,用在特定的场景里,也非常实用。
keep-alive相关的钩子
keep-alive是Vue的一个内置组件,用来缓存组件的状态,避免组件被重复渲染,比如做一个电商网站的搜索结果页和商品详情页,当用户从搜索结果页点击商品进入详情页,再返回搜索结果页的时候,希望搜索结果页的滚动条位置、筛选条件、已加载的商品列表都保留下来,这时候就可以用keep-alive包裹搜索结果页。 keep-alive包裹的组件会多两个生命周期钩子:
- onActivated:当组件被keep-alive缓存之后再次激活(显示在页面上)时触发,相当于普通组件的onMounted,但只会在第一次挂载和每次激活的时候触发,你可以在这里做一些“重新加载实时数据”的操作,比如刚才的电商搜索结果页,激活的时候可以重新请求一下商品的库存信息。
- onDeactivated:当组件被keep-alive缓存起来(隐藏在后台)时触发,相当于普通组件的onBeforeUnmount,但不会销毁组件实例,你可以在这里做一些“暂停操作”的操作,比如暂停倒计时组件的定时器,或者暂停视频的播放。
用script setup语法糖的话,这两个钩子需要从vue里导入,再举个电商搜索结果页的例子:
// App.vue
<template>
<router-view v-slot="{ Component }">
<keep-alive include="SearchResult"> <!-- 只缓存SearchResult组件 -->
<component :is="Component" />
</keep-alive>
</router-view>
</template>
// SearchResult.vue
<template>
<div ref="listRef" style="height: 600px; overflow-y: auto;">
<!-- 商品列表 -->
</div>
<!-- 筛选条件 -->
</template>
<script setup>
import { ref, onMounted, onActivated, onDeactivated } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const listRef = ref(null)
let scrollTop = 0
// 第一次挂载的时候请求商品列表
onMounted(() => {
// 调用请求接口的函数
})
// 每次激活的时候重新请求库存信息,并恢复滚动条位置
onActivated(() => {
// 调用请求库存信息的函数
if (listRef.value) {
listRef.value.scrollTop = scrollTop
}
})
// 每次缓存起来的时候保存滚动条位置
onDeactivated(() => {
if (listRef.value) {
scrollTop = listRef.value.scrollTop
}
})
</script>
哦对,刚才那个切换页面保存滚动条位置的问题,用keep-alive的这两个钩子就完美解决了,比普通组件的onBeforeUpdate和onUpdated好用多了。
错误处理相关的钩子
Vue3还有两个错误处理相关的钩子:
- onErrorCaptured:这个钩子用来捕获后代组件抛出的错误,相当于一个“错误边界”,可以防止整个应用因为一个组件的错误而崩溃,你可以在这里显示一个错误提示页面,或者把错误上报到后端的监控系统。
- onRenderTracked和onRenderTriggered:这两个钩子是Vue3新增的调试钩子,用来追踪响应式数据的依赖变化,开发环境下才有用,生产环境下会被自动移除。
- onRenderTracked:当Vue追踪到一个响应式数据的依赖时触发,比如你在template里用了某个ref的数据,Vue会在第一次渲染的时候追踪这个依赖,此时就会触发这个钩子。
- onRenderTriggered:当Vue因为一个响应式数据的变化而触发重新渲染时触发,比如你修改了那个ref的数据,Vue会重新渲染页面,此时就会触发这个钩子。
这两个调试钩子非常有用,当你遇到页面无限循环更新的问题时,可以用它们来追踪到底是哪个响应式数据的变化导致的,举个简单的例子:
<template>
<div>{{ count }}</div>
<button @click="count++">+1</button>
</template>
<script setup>
import { ref, onRenderTracked, onRenderTriggered } from 'vue'
const count = ref(0)
// 开发环境下,第一次渲染的时候会触发,追踪到count的依赖
onRenderTracked((event) => {
console.log('追踪到依赖:', event)
})
// 开发环境下,每次点击按钮修改count的时候会触发,重新渲染
onRenderTriggered((event) => {
console.log('触发重新渲染:', event)
})
</script>
你可以在浏览器的控制台里看一下event对象,里面会告诉你是哪个响应式数据的变化导致的。
Vue3生命周期钩子的正确使用姿势
咱们再总结一下Vue3生命周期钩子的正确使用姿势,避免踩坑:
- 能用组合式API就用组合式API,新项目主流都是组合式的,更灵活,更适合复用代码。
- setup函数替代beforeCreate和created,不要在新项目里用这两个选项式的钩子。
- 操作真实DOM、初始化第三方库、发起依赖DOM的请求,用onMounted。
- 清理全局资源(事件监听、定时器、WebSocket、第三方库实例),用onBeforeUnmount,这点非常重要,防止内存泄漏。
- 用keep-alive缓存组件状态,用onActivated和onDeactivated。
- 遇到页面无限循环更新的问题,用onRenderTracked和onRenderTriggered调试(仅开发环境)。
- 千万不要在onBeforeUpdate和onUpdated里直接修改导致更新的那个响应式数据,否则会无限循环。
好啦,今天关于Vue3生命周期钩子函数的分享就到这里,如果你还有什么疑问,可以在评论区留言,咱们一起讨论。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



