Vue3生命周期到底和Vue2有啥不一样?setup里怎么用?新手避坑指南全在这里
刚从Vue2转过来或者直接学Vue3的同学,有没有对着生命周期钩子的名字犯晕?一会儿是beforeCreate一会儿setup一会儿beforeMount变onBeforeMount?还有setup到底在什么阶段执行?销毁钩子为啥分unmounted和beforeUnmount?今天咱们用聊天式的问答,把这些坑一个个踩平,以后写代码再也不敢随便放钩子逻辑。
先搞懂:Vue2和Vue3的生命周期到底差在哪?
咱们先回忆下Vue2的经典8个钩子——beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed,后来加了keep-alive的activated、deactivated,还有errorCaptured,但核心的就是前面8个,那Vue3改了啥? 改名销毁阶段的钩子:beforeDestroy变成beforeUnmount,destroyed变成unmounted,这个很好理解,就是语义化更准——Vue3说组件不再是“销毁”得渣都不剩,而是“卸载”出DOM,以后可能还要再挂载(比如用v-if切换),钩子名字就贴合这个过程了。 新增了setup专属钩子前缀on-:Options API里的钩子还是原来的(除了销毁),但Composition API里不能直接写beforeCreate这种函数,要改成onBeforeMount这种带on开头的,而且得从vue里import进来,不然会报错找不到变量。 也是最关键的——beforeCreate和created被“干掉”了一半:为啥是一半?因为Options API里还能写这俩,但Composition API里没有对应的onBeforeCreate、onCreated,直接用setup函数代替这俩阶段的逻辑了,这个改动是Vue3最核心的优化之一,因为Composition API要把状态和逻辑集中管理,不需要等组件实例完全创建再写状态了。
再掰碎:每个Vue3生命周期钩子具体做啥?用在哪?
咱们按组件从出生到卸载的完整流程说,不管Options还是Composition API,原理是一样的,只是写法不同。
准备出生阶段
准备阶段主要是给组件“打底”,比如注册响应式数据、绑定函数,但这时候还没有DOM,也拿不到$el、$refs这些东西。
beforeCreate(Options API专属)
啥时候执行?组件实例刚被创建,还没有做数据观测和事件初始化——也就是说,这里访问data里的数据、computed属性、methods里的方法,全都是undefined,连this都指向的是空的或者Vue实例的原型链?其实新手基本上不用写这个钩子,因为啥也干不了,完全可以换成setup或者created。 举个踩坑的例子:新手刚转过来,在beforeCreate里写this.name = '张三',结果到页面上根本显示不出来,还报错找不到name,就是因为这个阶段数据观测还没开始,你赋的值根本不会变成响应式的,也不会挂载到实例上。
created(Options API专属)
啥时候执行?数据观测、事件初始化都做完了,但还没有编译模板,没有挂载DOM——也就是说,这里可以访问data、computed、methods、this了,但拿不到真实的DOM节点,比如你想用document.getElementById或者this.$refs.box去获取元素,全都是null。 那这个阶段能干啥?最适合做不需要DOM的数据请求,比如登录后拉取用户的个人信息、拉取列表的第一页数据,因为这些请求只需要用到API,不需要操作页面。
setup(Composition API核心入口)
啥时候执行?敲黑板!新手最容易搞错的点——setup是在beforeCreate之前执行的!哦不对,官方文档说的是“在组件实例创建之前执行”,准确来讲是在beforeCreate钩子执行的前一刻,这时候组件实例还没完全生成,所以setup里不能用this! 那setup里能干啥?这是Vue3 Composition API的“主战场”——所有Composition API的东西都在这里写,比如ref、reactive定义响应式数据,computed、watch、watchEffect监听数据变化,还有那些带on开头的钩子也得在这里注册,甚至子组件传过来的props、父组件传的事件emit,都得在这里通过参数拿到(setup有两个参数:props和context,context里有attrs、slots、emit)。 举个例子,比如之前在created里写的数据请求,在setup里可以这么写: import { ref, onMounted } from 'vue' import axios from 'axios'
export default { setup() { const userInfo = ref({}) const getUserInfo = async () => { const res = await axios.get('/api/user') userInfo.value = res.data } // 注意:这里不能直接调用getUserInfo吗?其实可以,但更稳妥的是放在onMounted里?哦不对,这个要看需求——如果你的数据请求不需要DOM,直接在setup顶层调用也行,就像created里一样;但如果请求回来的数据需要操作DOM(比如给轮播图赋值后初始化),那就得放在onMounted里了。 // 先直接调用试试?当然可以,但后面我们讲mounted的时候再对比。 getUserInfo() return { userInfo } } } 这里要注意两个坑:第一,setup里定义的响应式数据、函数,必须return出去,不然模板里用不了;第二,props是响应式的,但不能解构,不然会丢失响应式,要解构的话得用toRefs或者toRef。
挂载到页面阶段
挂载阶段是组件从“虚拟模板”变成“真实DOM”的过程,这时候就能拿到$el、$refs这些东西了。
beforeMount/onBeforeMount
啥时候执行?模板已经编译成了虚拟DOM,但还没有挂载到真实的页面上——也就是说,这里还是拿不到真实的DOM节点,比如this.$refs.box还是null,但虚拟DOM已经生成了,不过新手基本上也不用操作虚拟DOM,所以这个钩子用得也不多。
mounted/onMounted
啥时候执行?敲黑板!新手最常用的钩子——虚拟DOM已经挂载到真实的页面上了,这时候可以拿到真实的DOM节点,可以操作页面(比如给轮播图初始化、给下拉框设置默认值、调用第三方库需要DOM的API),也可以继续做数据请求(比如请求回来的数据需要结合DOM操作)。 那刚才的例子,如果请求回来的userInfo里有头像的URL,而我们要给头像加个懒加载,懒加载库需要拿到真实的img节点,那就得把getUserInfo放在onMounted里: import { ref, onMounted } from 'vue' import axios from 'axios' import LazyLoad from 'xxx-lazyload'
export default { setup() { const userInfo = ref({}) const avatarRef = ref(null) const getUserInfo = async () => { const res = await axios.get('/api/user') userInfo.value = res.data // 等响应式数据更新后,DOM还没立刻渲染,所以得用nextTick await nextTick() LazyLoad.init(avatarRef.value) } onMounted(() => { getUserInfo() }) return { userInfo, avatarRef } } } 这里又有一个新手坑:mounted/onMounted里只能保证组件自身的DOM挂载完成,不能保证子组件的DOM也挂载完成,如果子组件是异步加载的(比如用defineAsyncComponent),那得用子组件的mounted钩子或者父组件的onMounted里加个setTimeout/nextTick,但更稳妥的是用子组件的emit事件告诉父组件“我挂载完了”。
更新阶段
更新阶段是当组件的响应式数据发生变化时触发的,这里要注意区分:只有当数据变化导致模板重新渲染时,才会触发更新钩子,如果数据变化但模板里没用到,就不会触发。
beforeUpdate/onBeforeUpdate
啥时候执行?响应式数据已经变化,但虚拟DOM还没重新渲染,真实DOM也没更新——也就是说,这里可以拿到更新前的DOM状态,比如用户输入了新的内容,beforeUpdate里可以拿到旧的input值,有时候用来做表单的“撤销上一步”功能。
updated/onUpdated
啥时候执行?虚拟DOM已经重新渲染,真实DOM也更新完成——也就是说,这里可以拿到更新后的DOM状态,但要注意不要在这里直接修改导致模板重新渲染的响应式数据,不然会陷入无限循环! 举个无限循环的踩坑例子: import { ref, onUpdated } from 'vue'
export default { setup() { const count = ref(0) onUpdated(() => { count.value++ // 这里修改了count,会触发重新渲染,然后又触发onUpdated,又修改count,无限循环! }) return { count } } } 那这个阶段能干啥?比如更新后调整一下滚动条的位置、更新第三方库的数据(比如ECharts的setOption),但一定要注意不要直接修改触发更新的数据,或者加个判断条件,只有满足特定条件才修改。
卸载阶段
卸载阶段是组件从真实DOM上移除的过程,这里要注意清理“副作用”,比如清除定时器、取消事件监听、销毁第三方库的实例,不然会造成内存泄漏!
beforeUnmount/onBeforeUnmount
啥时候执行?组件还没有从真实DOM上移除,实例还是完全可用的——也就是说,这里可以拿到this、$el、$refs,是清理副作用的最佳时机! 举个清理定时器的例子: import { ref, onMounted, onBeforeUnmount } from 'vue'
export default { setup() { const timer = ref(null) const count = ref(0) onMounted(() => { timer.value = setInterval(() => { count.value++ }, 1000) }) onBeforeUnmount(() => { clearInterval(timer.value) // 组件卸载前清除定时器,防止内存泄漏 }) return { count } } }
unmounted/onUnmounted
啥时候执行?组件已经从真实DOM上移除,实例已经被销毁,所有的事件监听、响应式数据绑定都被解除了——也就是说,这里拿不到this、$el、$refs了,基本上不用写这个钩子,所有的清理工作都应该在beforeUnmount里完成。
特殊钩子(keep-alive和错误捕获)
除了上面的核心钩子,Vue3还有几个特殊钩子,新手可能偶尔会用到。
activated/onActivated、deactivated/onDeactivated
这俩是keep-alive组件专属的钩子——当keep-alive包裹的组件被激活(比如从其他页面切回来)时,触发activated/onActivated;当被缓存(比如切到其他页面)时,触发deactivated/onDeactivated。 举个例子,比如你的首页是keep-alive包裹的,切到其他页面时不需要拉取数据,但切回来时需要刷新一下列表,就可以在activated里写: import { ref, onActivated } from 'vue' import axios from 'axios'
export default { setup() { const list = ref([]) const getList = async () => { const res = await axios.get('/api/list') list.value = res.data } onActivated(() => { getList() // 每次切回首页都刷新列表 }) return { list } } } 这里要注意:keep-alive包裹的组件,第一次挂载时还是会触发beforeMount、mounted,切回来时只会触发activated,不会再触发beforeMount、mounted;切走时只会触发deactivated,不会触发beforeUnmount、unmounted。
errorCaptured/onErrorCaptured
这个钩子是用来捕获子孙组件的错误的——当子孙组件抛出错误时,会先触发errorCaptured/onErrorCaptured,如果这个钩子返回false,错误就不会继续向上传播(不会触发app.config.errorHandler)。 举个例子,比如你的组件里有个异步加载的子组件,可能会报错,就可以用这个钩子来捕获: import { ref, onErrorCaptured } from 'vue' import AsyncChild from './AsyncChild.vue'
export default {
components: { AsyncChild },
setup() {
const errorMsg = ref('')
onErrorCaptured((err, instance, info) => {
errorMsg.value = 子组件出错了:${err.message}
return false // 阻止错误继续向上传播
})
return { errorMsg }
}
}
然后在模板里可以显示errorMsg,给用户一个友好的提示。
serverPrefetch(SSR专属)
这个钩子是服务端渲染专属的,客户端渲染不会触发,主要是用来在服务端预取数据的,新手如果不做SSR的话,可以不用了解。
新手必看:Composition API和Options API的钩子怎么选?
很多新手会问:既然Vue3同时支持Options API和Composition API,那我应该用哪种写法的钩子? 其实这个问题没有标准答案,要看你的项目情况: 如果你的项目是Vue2转过来的小项目,或者你是刚学Vue3的新手,可以先用Options API的钩子,毕竟和Vue2的写法差不多,容易上手; 如果你的项目是Vue3的新项目,或者你的组件逻辑比较复杂,需要复用状态和逻辑,那一定要用Composition API的钩子,因为Composition API可以把相关的状态和逻辑集中在一起,比如一个搜索功能的状态(搜索关键词、搜索结果、加载状态)、逻辑(搜索请求、防抖),可以放在一个useSearch函数里,然后在多个组件里复用,而Options API里这些东西是分散在data、methods、computed、watch里的,逻辑复杂了之后很难维护。
Vue3生命周期的核心要点
今天讲了这么多,最后给新手总结几个核心要点,方便大家记忆:
- 销毁钩子改名:beforeDestroy→beforeUnmount,destroyed→unmounted;
- Composition API加on-前缀,并从vue里import;
- setup替代beforeCreate和created,setup里不能用this;
- mounted/onMounted是操作DOM、结合DOM做数据请求的最佳时机;
- beforeUnmount/onBeforeUnmount是清理副作用的最佳时机;
- updated/onUpdated里不要直接修改触发更新的数据;
- activated/onActivated、deactivated/onDeactivated是keep-alive专属的;
- Options API适合小项目、新手,Composition API适合大项目、逻辑复杂、需要复用的组件。
好了,今天的Vue3生命周期就讲到这里,大家有没有收获?如果还有不懂的地方,可以在评论区留言,我会一一解答。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


