Code前端首页关于Code前端联系我们

一、先分清「Vue 组件」和「小程序/APP 页面」的生命周期差异

terry 11小时前 阅读数 11 #Vue

不少刚接触 Vue2 的同学会疑惑:“Vue2 里有没有 onshow ?想在页面显示的时候执行逻辑该咋做?” 先把关键问题讲清楚——Vue2 核心框架里没有「onshow」这个原生的生命周期钩子,这和微信小程序、原生 APP 的页面生命周期逻辑不一样,因为 Vue 是单页面应用(SPA)框架,“页面”本质是路由对应的组件,得结合场景用不同方法模拟「页面显示时触发逻辑」的效果,下面从概念区分、常见实现方案、场景选择三个维度拆解答案。

很多同学混淆 onshow ,是因为用过微信小程序、uniapp 这类框架,比如小程序里的 `onShow` ,是**页面级生命周期**:不管是首次打开页面、从后台切回前台、从其他页面返回当前页,只要页面“显示”就会触发。

但 Vue2 是组件化框架,SPA 里的“页面切换”实际是「路由组件的销毁/重建/复用」,Vue 组件的原生生命周期(created、mounted、updated 等),关注的是组件自身的创建、挂载、更新,不是“页面是否被用户看到”这个场景,比如一个组件被 <code>&lt;keep-alive&gt;</code> 缓存后,切走再切回来时,mounted 不会重新执行——但这时候用户“看到”了组件,却没触发新的生命周期,这就是需要模拟「onshow」的核心原因。

实现「页面显示时执行逻辑」的 5 种常用方案

方案 1:利用 <code>&lt;keep-alive&gt;</code><code>activated</code> 钩子

如果你的“页面”(路由组件)被 <code>&lt;keep-alive&gt;</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>&lt;keep-alive&gt;</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>&lt;keep-alive&gt;</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>&lt;keep-alive include="GoodsList"&gt;</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前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门