一、先分清「Vue 组件」和「小程序/APP 页面」的生命周期差异
不少刚接触 Vue2 的同学会疑惑:“Vue2 里有没有 onshow ?想在页面显示的时候执行逻辑该咋做?” 先把关键问题讲清楚——Vue2 核心框架里没有「onshow」这个原生的生命周期钩子,这和微信小程序、原生 APP 的页面生命周期逻辑不一样,因为 Vue 是单页面应用(SPA)框架,“页面”本质是路由对应的组件,得结合场景用不同方法模拟「页面显示时触发逻辑」的效果,下面从概念区分、常见实现方案、场景选择三个维度拆解答案。
很多同学混淆 onshow ,是因为用过微信小程序、uniapp 这类框架,比如小程序里的 `onShow
` ,是**页面级生命周期**:不管是首次打开页面、从后台切回前台、从其他页面返回当前页,只要页面“显示”就会触发。
但 Vue2 是组件化框架,SPA 里的“页面切换”实际是「路由组件的销毁/重建/复用」,Vue 组件的原生生命周期(created、mounted、updated 等),关注的是组件自身的创建、挂载、更新,不是“页面是否被用户看到”这个场景,比如一个组件被 <code><keep-alive></code>
缓存后,切走再切回来时,mounted 不会重新执行——但这时候用户“看到”了组件,却没触发新的生命周期,这就是需要模拟「onshow」的核心原因。
实现「页面显示时执行逻辑」的 5 种常用方案
方案 1:利用 <code><keep-alive></code>
的 <code>activated</code>
钩子
如果你的“页面”(路由组件)被 <code><keep-alive></code>
缓存(比如首页 → 详情页 → 回首页,首页组件不销毁),每次组件从缓存中被“激活”显示时,<code>activated</code>
钩子会触发(而 created、mounted 只在组件首次创建时执行一次)。
举个实际场景:电商 App 的商品列表页,点进详情页再返回列表页时,希望列表自动刷新最新库存,这时候给列表组件加 <code>activated</code>
即可:
<script> export default { name: 'GoodsList', activated() { // 每次组件被激活显示时,重新拉取数据 this.fetchGoodsData() }, methods: { fetchGoodsData() { // 调用接口获取商品列表 } } } </script>
注意:<code>activated</code>
生效的前提是组件被 <code><keep-alive></code>
包裹,如果项目里用了路由级缓存(比如在路由配置中给组件加 <code>meta: { keepAlive: true }</code>
),那这个钩子才会在“组件显示”时触发,如果组件没被缓存,activated 不会执行,这时候得换其他方案。
方案 2:Vue Router 的「路由守卫」
路由守卫能在「路由切换的不同阶段」执行逻辑,适合“从其他页面切回当前页时触发逻辑”的场景,常用的有组件内守卫和全局守卫两类。
① 组件内守卫:beforeRouteEnter / beforeRouteUpdate
<code>beforeRouteEnter</code>
:路由进入当前组件之前触发(此时组件实例<code>this</code>
还没创建,所以要通过<code>next(vm => { ... })</code>
访问组件实例)。适合首次进入组件时执行逻辑,但如果组件被复用(比如路由参数变化,组件不销毁),这个钩子不会重复触发。<code>beforeRouteUpdate</code>
:当前组件复用、路由参数变化时触发(比如商品列表页,从<code>/list?id=1</code>
跳转到<code>/list?id=2</code>
,组件没销毁但路由参数变了)。适合路由参数变化时,刷新页面数据。
举个例子:文章详情页,从文章 A 跳转到文章 B(路由是 <code>/article/:id</code>
),需要每次切换都重新拉取文章内容,用 <code>beforeRouteUpdate</code>
实现:
<script> export default { name: 'ArticleDetail', beforeRouteUpdate(to, from, next) { // to.params.id 是新的文章ID this.fetchArticle(to.params.id) next() // 必须调用 next() 放行路由 }, methods: { fetchArticle(id) { // 根据ID请求文章详情 } } } </script>
② 全局守卫:afterEach
<code>router.afterEach</code>
是全局后置钩子,所有路由切换完成后都会触发,如果想在“切到某个页面时执行逻辑”,可以结合路由的 <code>meta</code>
字段做标记。
希望用户从任意页面切回「个人中心页」时,自动刷新用户信息,在路由配置里给个人中心页加 <code>meta</code>
:
// 路由配置 router.js { path: '/profile', name: 'Profile', component: () => import('./views/Profile.vue'), meta: { needRefresh: true } // 标记需要刷新 }
然后在全局路由守卫里判断:
// main.js 里配置全局守卫 router.afterEach((to, from) => { if (to.meta.needRefresh) { // 假设 store 里有刷新用户信息的 action store.dispatch('user/fetchUserInfo') } })
这种方式的优势是全局统一管理,但要注意 <code>afterEach</code>
不支持 <code>next()</code>
拦截,只能做“执行逻辑”类操作。
方案 3:监听浏览器「标签页可见性」(visibilitychange 事件)
如果想实现“页面从后台切回前台(比如用户切到其他浏览器标签,再切回来)时触发逻辑”,得用浏览器原生的 <code>visibilitychange</code>
事件,这个场景和“路由切换”无关,是浏览器层面的页面可见性变化。
举个场景:后台管理系统,用户切回标签页时自动检查登录状态是否过期,在组件的 <code>mounted</code>
里添加监听,<code>beforeDestroy</code>
里移除监听(避免内存泄漏):
<script> export default { name: 'Dashboard', mounted() { document.addEventListener('visibilitychange', this.handleVisibilityChange) }, beforeDestroy() { document.removeEventListener('visibilitychange', this.handleVisibilityChange) }, methods: { handleVisibilityChange() { if (document.visibilityState === 'visible') { // 页面从后台切回前台,执行逻辑 this.checkLoginStatus() } }, checkLoginStatus() { // 调用接口检查登录态 } } } </script>
注意:<code>visibilitychange</code>
是全局事件,如果多个组件同时监听,要做好逻辑隔离,手机端 App 里的 WebView 切换后台,部分安卓机型可能不支持这个事件,需结合原生桥接方案处理。
方案 4:监听路由变化 + 组件挂载状态
如果你的“页面显示”逻辑,是当路由切换到当前组件时执行(不管组件是否被缓存),可以结合 <code>watch $route</code>
和组件的 <code>mounted</code>
状态。
思路:在组件的 <code>watch</code>
里监听 <code>$route</code>
变化,当路由指向当前组件时,执行逻辑,同时要避免组件未挂载时执行(比如路由切换太快导致 <code>this</code>
指向错误)。
举个例子:博客列表页,从分类页切回列表页时刷新数据,代码如下:
<script> export default { name: 'BlogList', data() { return { isMounted: false // 标记组件是否已挂载 } }, mounted() { this.isMounted = true this.fetchBlogs() // 首次挂载时拉取数据 }, beforeDestroy() { this.isMounted = false }, watch: { '$route'(to, from) { // 路由切换到当前组件,且组件已挂载时,执行逻辑 if (to.name === 'BlogList' && this.isMounted) { this.fetchBlogs() } } }, methods: { fetchBlogs() { // 拉取博客列表数据 } } } </script>
这种方案的灵活性高,但需要自己维护 <code>isMounted</code>
状态,防止组件销毁后还执行逻辑导致报错。
方案 5:结合「页面滚动位置」的特殊场景(进阶)
有些场景下,“页面显示”还涉及“滚动条回到之前位置”这类交互(比如移动端列表页下滑后切到详情页,返回时列表滚动位置还原),这时候除了用 <code>activated</code>
,还得结合 <code>keep-alive</code>
的 <code>deactivated</code>
钩子记录滚动位置,再在 <code>activated</code>
时还原。
示例代码(简化版):
<script> export default { name: 'ScrollList', data() { return { scrollTop: 0 // 记录滚动位置 } }, activated() { // 组件激活时,还原滚动位置 this.$nextTick(() => { window.scrollTo(0, this.scrollTop) }) }, deactivated() { // 组件缓存时,记录当前滚动位置 this.scrollTop = document.documentElement.scrollTop } } </script>
这种方案属于“页面显示”+“交互还原”的组合场景,需要 <code>keep-alive</code>
配合,适合对用户体验要求高的列表类页面。
不同场景下怎么选?一张表帮你快速决策
很多同学学完方法后,还是分不清该用哪个,这里整理场景-方案匹配表,直接对应业务需求选工具:
业务场景 | 推荐方案 | 原因 |
---|---|---|
组件被 keep-alive 缓存,切回时执行逻辑(如列表页返回刷新) | activated 钩子 | activated 是 keep-alive 专属钩子,精准对应“组件激活显示”场景 |
路由参数变化,组件复用(如文章详情页切换不同文章) | beforeRouteUpdate 守卫 | 组件没销毁,只是路由参数变了,beforeRouteUpdate 能捕获参数变化 |
浏览器标签从后台切回前台(如后台系统检查登录态) | visibilitychange 事件 | 这是浏览器原生事件,专门监听页面可见性变化 |
全局控制多个页面的“切回时刷新”(如所有页面切回都检查权限) | router.afterEach + 路由 meta | 全局守卫统一管理,路由 meta 标记需要触发逻辑的页面 |
组件未被缓存,每次路由切到该组件都执行逻辑 | watch $route + 挂载状态 | watch 能捕获路由变化,挂载状态避免组件销毁后执行逻辑 |
页面显示时还原滚动位置、表单状态等交互 | activated + deactivated + keep-alive | deactivated 记录状态,activated 还原状态,依赖 keep-alive 缓存 |
避坑指南:这些细节容易踩雷
知道了方案,还要避开实际开发的“暗坑”:
坑 1:activated 和 mounted 搞混
<code>mounted</code>
是组件首次挂载到 DOM 时执行(只执行一次);<code>activated</code>
是组件被 <code><keep-alive></code>
缓存后,每次从缓存中激活显示时执行(可能执行多次),如果组件没被缓存,activated 永远不会触发,这时候写了 activated 里的逻辑等于白写。
坑 2:路由守卫里的 this 指向错误
<code>beforeRouteEnter</code>
执行时,组件实例还没创建,<code>this</code>
是 <code>undefined</code>
,必须通过 <code>next(vm => { vm.xxx() })</code>
才能访问组件实例,而 <code>beforeRouteUpdate</code>
和 <code>beforeRouteLeave</code>
执行时,组件实例已存在,<code>this</code>
能正常访问。
坑 3:visibilitychange 事件重复监听
如果多个组件都监听 <code>visibilitychange</code>
,每次切回页面时,所有组件的监听逻辑都会执行,要嘛做全局事件总线统一管理,要嘛在组件销毁时移除事件监听(用 <code>beforeDestroy</code>
),否则会导致逻辑重复执行甚至内存泄漏。
坑 4:keep-alive 的 include/exclude 配置错误
如果用 <code><keep-alive include="GoodsList"></code>
来缓存指定组件,必须保证组件的 <code>name</code>
选项和 include 的值完全一致(区分大小写),很多同学因为组件 name 没写对,导致 activated 不触发,排查半天找不到原因。
Vue2 里没有 onshow,但有更灵活的实现方式
回到最开始的问题:Vue2 没有原生的 onshow ,但通过 <code>activated</code>
、路由守卫、visibilitychange 等方案,能覆盖“页面显示时执行逻辑”的所有场景,核心是先明确“显示”的定义——是组件被激活显示?是路由切换到该页面?还是浏览器标签切回前台?不同定义对应不同技术方案。
实际开发中,建议先画清楚页面流转图:哪些页面需要“显示时触发逻辑”?触发逻辑的时机是路由切换、组件激活,还是浏览器可见性变化?把场景拆解清楚后,再从上面的方案里选工具,就能避免“为了用而用”的尴尬。
最后延伸个小思考:Vue3 里对 keep-alive 的钩子做了调整(activated 变成组合式 API 的 onActivated ),但核心思路和 Vue2 一致,如果以后升级到 Vue3 ,这些实现逻辑的“设计思想”也能复用,这就是理解原理比死记 API 更重要的地方~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。