Vue Router生命周期包含哪些核心环节?
p不少刚接触Vue Router的同学,总会疑惑它的生命周期咋运作,和Vue组件自身生命周期咋配合,开发时又该咋利用这些阶段做权限控制、数据加载这些事儿,今天就借着问答的形式,把Vue Router生命周期的关键逻辑、实际用法掰碎了聊聊。
要理解Vue Router的生命周期,得先明白“导航过程”的全貌,从用户点击链接或调用router.push
开始,到新页面渲染完成,整个流程里嵌套着不同层级的“守卫”(钩子函数)和Vue组件自身的生命周期钩子。
核心环节可以分成这几类:
- 全局守卫:作用于整个路由系统,比如
router.beforeEach
(导航触发后立即执行)、router.beforeResolve
(组件解析后、导航确认前)、router.afterEach
(导航完成后)。 - 路由独享守卫:只针对某一条路由规则,用
beforeEnter
定义在路由配置里。 - 组件内守卫:组件自己身上的路由钩子,像
beforeRouteEnter
(进入路由前,组件实例还没创建)、beforeRouteUpdate
(路由参数变化但组件复用)、beforeRouteLeave
(离开路由前)。 - Vue组件自身生命周期:比如
created
、mounted
、beforeDestroy
这些,路由切换时组件的创建、销毁会触发它们。
举个直观的流程例子:用户从首页跳转到文章详情页,流程是这样的:
- 调用
router.push('/article')
,导航开始; - 触发全局beforeEach(可以在这里判断权限,比如是否登录);
- 匹配到
/article
的路由规则,触发该路由的独享beforeEnter(如果有的话,比如检查文章是否公开); - 解析目标组件(如果是异步组件,这时候才会加载);
- 触发目标组件的beforeRouteEnter(此时组件实例
this
还没生成); - 触发全局beforeResolve(确保所有守卫都执行完,避免遗漏);
- 确认导航,销毁旧组件(如果有的话),触发旧组件的
beforeDestroy
等; - 创建新组件实例,触发新组件的
created
、mounted
; - 触发全局afterEach(可以在这里改页面标题、埋点统计)。
全局守卫在生命周期里起啥作用?咋用?
全局守卫是“管全局”的,所有路由切换都会经过它们,适合做全局权限控制、埋点、页面跳转拦截这些通用逻辑。
-
router.beforeEach:导航刚触发就执行,是“第一道关卡”。
场景举例:用户访问需要登录的页面时,判断store
里的登录状态,没登录就跳转到登录页,代码大概长这样:router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !isLogin()) { next('/login'); // 没登录,跳登录页 } else { next(); // 放行 } });
注意
next
必须调用,否则导航会卡住;而且别重复调用next
,比如在if
和else
里都写会报错。 -
router.beforeResolve:它在“组件内守卫”和“异步路由组件加载完成后”执行,比
beforeEach
晚一步。
作用是确保所有异步操作完成后再确认导航,避免“组件还没加载完就跳转”的问题,比如有个路由是异步加载的(component: () => import('./Article.vue')
),beforeResolve
会等这个组件加载完再执行后续逻辑。 -
router.afterEach:导航完成后执行,此时页面已经渲染好了。
适合做不影响导航的收尾工作,比如修改页面标题(document.title = to.meta.title
)、统计页面访问次数、重置滚动条位置(window.scrollTo(0, 0)
),因为它不影响导航流程,所以没有next
参数。
路由独享守卫和全局守卫有啥区别?啥场景用?
路由独享守卫叫beforeEnter
,是写在单个路由配置对象里的,只有匹配到这个路由时才会触发,作用范围更“精准”。
区别很明显:全局守卫管所有路由,beforeEnter
只管自己,场景上,适合单一路由的特殊逻辑,全局守卫里写太冗余的情况。
举个例子:后台系统里,“订单编辑页”需要额外验证用户是否有编辑权限(即使已经登录,还要检查角色),这时候给/order/edit
这条路由加beforeEnter
:
const routes = [ { path: '/order/edit', component: OrderEdit, beforeEnter: (to, from, next) => { if (hasEditPermission()) { next(); } else { next('/order/list'); // 没权限跳订单列表 } } } ];
如果把这段逻辑放到全局beforeEach
里,得判断to.path
是不是/order/edit
,代码会变繁琐;用beforeEnter
就只影响这一个路由,清爽很多。
组件内的路由钩子咋和组件自身生命周期配合?
组件内的三个路由钩子(beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
),和created
、mounted
这些Vue自身生命周期钩子是“穿插执行”的,理解它们的顺序和作用,才能在组件里精准控制逻辑。
-
beforeRouteEnter:进入路由前触发,组件实例还没创建(所以
this
是undefined
)。
啥时候用?适合进入页面前预加载数据,因为组件还没创建,要拿数据给组件用,得用next
的回调:export default { beforeRouteEnter(to, from, next) { axios.get('/api/article/' + to.params.id).then(res => { next(vm => { // vm是组件实例,相当于this vm.article = res.data; }); }); }, created() { // 这里也能请求数据,但beforeRouteEnter里请求能让数据在组件渲染前准备好,避免页面闪烁 } };
执行顺序:
beforeRouteEnter
→created
→mounted
。 -
beforeRouteUpdate:路由参数变化,但组件复用的时候触发(比如从
/user/1
跳到/user/2
,用户组件复用)。
场景:根据新参数更新组件数据,比如用户ID变了,要重新拉取用户信息:beforeRouteUpdate(to, from, next) { this.fetchUser(to.params.id); // 直接用this,因为组件已经创建了 next(); }
执行顺序:
beforeRouteUpdate
→updated
(如果有数据更新触发updated
的话)。 -
beforeRouteLeave:离开当前路由前触发,可以阻止导航(比如表单没保存)。
例子:用户填了表单没提交,离开时弹窗确认:beforeRouteLeave(to, from, next) { if (this.formDirty) { if (window.confirm('表单没保存,确定离开?')) { next(); // 确认就走 } else { next(false); // 取消就留在当前页 } } else { next(); // 表单干净,直接走 } }
执行顺序:离开时,先触发
beforeRouteLeave
,再触发旧组件的beforeDestroy
。
实际开发中,咋结合路由生命周期做权限管理?
权限管理是路由生命周期最常用的场景之一,要分全局权限、路由级权限、组件内权限三层来搞,互相配合。
-
全局权限(全局守卫):
用router.beforeEach
拦截所有路由,判断用户是否登录、是否有基础权限,比如后台系统,除了登录页,其他页都要登录:router.beforeEach((to, from, next) => { const isLogin = localStorage.getItem('token'); if (to.path !== '/login' && !isLogin) { next('/login'); } else { next(); } });
-
路由级权限(路由独享守卫):
某些路由需要更细的权限,管理员面板”只有管理员角色能进,给对应的路由加beforeEnter
:{ path: '/admin', component: AdminPanel, beforeEnter: (to, from, next) => { const userRole = localStorage.getItem('role'); if (userRole === 'admin') { next(); } else { next('/403'); // 没权限跳403页面 } } }
-
组件内权限(组件内守卫):
组件渲染后,某些按钮或模块可能需要权限控制,比如文章编辑页,只有作者能编辑,在beforeRouteEnter
里判断:beforeRouteEnter(to, from, next) { const article = getArticle(to.params.id); if (article.authorId === currentUserId()) { next(); // 是作者,放行 } else { next('/article/' + to.params.id); // 不是作者,跳文章详情页 } }
三层配合起来,既能拦截未登录用户,又能限制不同角色的路由访问,还能在组件层面做细粒度控制。
路由生命周期里的数据预加载咋做?
数据预加载能让页面切换更流畅,避免“先渲染再请求数据导致的空白/闪烁”,关键是在导航完成前把数据准备好,这时候路由守卫就是天然的“预加载时机”。
常见做法有两种:
-
在全局/路由独享守卫里请求数据:
比如文章列表页,在router.beforeEach
或路由的beforeEnter
里请求数据,然后把数据存到Vuex或传递给组件,但要注意,这种方式数据是全局的,多个组件共享时要考虑状态管理。 -
在组件内的beforeRouteEnter里请求数据:
前面举过例子,用next(vm => {})
把请求到的数据传给组件实例,这种方式数据是组件级的,更灵活。
举个完整例子:用户进入“商品详情页”,需要先拿到商品ID,请求详情数据,再渲染页面。
// 路由配置 { path: '/product/:id', component: ProductDetail, // 也可以在beforeEnter里请求数据,存到Vuex beforeEnter: (to, from, next) => { fetchProduct(to.params.id).then(res => { store.commit('SET_PRODUCT', res.data); next(); }); } } // ProductDetail组件内 export default { data() { return { product: {} }; }, beforeRouteEnter(to, from, next) { // 也可以在这里请求,适合组件私有的数据 fetchProduct(to.params.id).then(res => { next(vm => { vm.product = res.data; }); }); }, mounted() { // 如果守卫里没请求,这里再请求就会有延迟,页面先渲染再填充数据 } };
两种方式各有优劣:全局/路由守卫里请求,数据能提前存到Vuex,多个组件能复用;组件内守卫请求,数据是组件自己的,更解耦,实际开发中可以根据数据的复用性选择。
路由切换时,组件销毁和重建的逻辑咋影响生命周期?
路由切换时,组件是否销毁重建,取决于路由对应的组件是否相同,这会直接影响Vue组件自身生命周期和路由钩子的执行顺序。
-
情况1:路由切换导致组件不同(比如从
/home
跳到/about
):
旧组件会经历:beforeRouteLeave
→beforeDestroy
→destroyed
;
新组件会经历:beforeRouteEnter
→created
→mounted
。 -
情况2:路由切换但组件复用(比如从
/user/1
跳到/user/2
,用户组件复用):
旧组件触发beforeRouteLeave
;
新组件触发beforeRouteUpdate
(代替beforeRouteEnter
,因为组件没销毁);
然后触发updated
(如果有数据更新)。
理解这个逻辑,能帮我们优化性能和避免Bug,比如组件复用场景下,数据不会自动更新,得在beforeRouteUpdate
里手动请求新数据,否则页面还显示旧用户的信息。
路由生命周期里有哪些容易踩的坑?
最后聊聊常见“踩坑点”,避开这些能少掉头发:
-
beforeRouteEnter里拿不到this:
因为组件实例还没创建,所以不能直接this.xxx
,必须通过next(vm => { vm.xxx = ... })
来访问组件实例。 -
全局守卫里next的错误使用:
比如在beforeEach
里忘记调用next()
,导航会一直pending;或者多次调用next
(比如if
和else
里都调用),会导致错误。 -
组件复用导致数据没更新:
路由参数变化但组件复用(如/user/:id
)时,created
和mounted
不会重新执行,所以要在beforeRouteUpdate
里处理数据更新,否则页面显示旧数据。 -
异步路由组件的加载时机:
用component: () => import('./MyComponent.vue')
这种异步加载时,组件加载是异步的,beforeResolve
会等加载完成后再执行,所以要注意导航等待时间,避免白屏太久。
Vue Router的生命周期是“导航流程 + 守卫 + 组件生命周期”的结合体,理解每个环节的触发时机和作用,才能在权限控制、数据加载、页面跳转拦截这些场景里游刃有余,实际开发中多结合业务场景调试,把各个钩子的执行顺序摸透,很多问题自然就解决啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。