vue-router有几种钩子函数?具体是什么及执行流程是怎样的?
前言
vue-router 有几种钩子函数?具体是什么及执行流程是怎样的?先上思维导图。
这是 Router 3 时代的历史思维导图,其中
afterEach的“返回值同 beforeEach”已经过时,且图未体现 Router 4 的failure参数;当前语义以下文说明为准。
Vue Router 的“钩子函数”就是导航守卫。“导航”表示路由正在改变,守卫可以放行、取消或重定向导航。按位置可分为:全局守卫、路由独享守卫、组件内守卫。
代码演示环境搭建
router/index.js:
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/a', component: () => import('../components/A.vue') },
{ path: '/b', component: () => import('../components/B.vue') },
{ path: '/c', component: () => import('../components/C.vue') }
]
})
export default router
main.js:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
页面 A、B、C 都可以使用下面的结构,标题分别替换为对应页面。这里显式导入通用组件,避免把未注册的 <Comp /> 当成浏览器自定义元素:
<script setup>
import Comp from './Comp.vue'
</script>
<template>
<div>
<h1>我是页面 A</h1>
<Comp />
</div>
</template>
通用组件:
<template>
<div>我是公用组件</div>
</template>
当前页面是这样的,有点朴素,凑合看吧,咱也不是来学习 CSS 的。

全局守卫
全局守卫注册在 router 对象上。
beforeEach
全局前置守卫在每次导航时触发:
router.beforeEach((to, from) => {
console.log('to:', to)
console.log('from:', from)
})
参数含义:
to:即将进入的目标路由对象。from:当前正要离开的路由对象。

Router 4 当前推荐通过返回值控制导航:
router.beforeEach((to) => {
if (!isAuthenticated && to.name !== 'login') {
return { name: 'login' }
}
if (to.meta.disabled) return false
// 无返回值、undefined 或 true:放行
// throw new Error('message'):取消并交给 router.onError()
})
Router 3 历史写法以及 Router 4 的兼容写法会使用第三个参数 next:
router.beforeEach((to, from, next) => {
next()
})
其中 next() 放行,next(false) 取消,next('/') 或 next({ name: 'login' }) 重定向,next(error) 终止并将错误交给 router.onError()。一旦使用 next,必须保证守卫的每条执行路径都恰好调用一次,否则可能阻塞导航或产生错误。
定义多个守卫
多个全局前置守卫按注册顺序执行,在异步守卫完成前导航保持等待:
router.beforeEach(async () => {
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('beforeEach 1')
})
router.beforeEach(async () => {
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('beforeEach 2')
})

beforeResolve
全局解析守卫在所有组件内守卫和异步路由组件解析之后、导航确认之前触发:
router.beforeResolve(async (to, from) => {
// 可在这里完成进入页面前的最终异步检查
})
它的放行、取消、重定向规则与 beforeEach 相同,也可以注册多个。
afterEach
afterEach 在每次导航结束后执行。对于成功导航,它位于 beforeRouteEnter 和 beforeResolve 之后;如果导航被取消、被新导航取代或重复导航,也会通过第三个 failure 参数报告结果。它不能改变导航本身,也不接收 next。
router.afterEach((to, from, failure) => {
if (!failure) {
console.log('导航成功:', to.fullPath)
}
})
await router.push() 返回空值通常表示成功,非空值可能是导航失败。可用 isNavigationFailure 和 NavigationFailureType 区分 aborted、cancelled、duplicated。
路由独享守卫
beforeEnter
beforeEnter 写在路由配置上,只在进入该路由时触发:
{
path: '/a',
component: () => import('../components/A.vue'),
beforeEnter: (to, from) => {
console.log('beforeEnter')
}
}
它不是简单地紧随 beforeEach:两者之间还可能先执行复用组件的 beforeRouteUpdate。仅改变 params、query 或 hash 时不会触发;嵌套路由中只在子路由之间切换,也不会重新触发未离开的父路由 beforeEnter。
组件内守卫
beforeRouteEnter
export default {
beforeRouteEnter(to, from, next) {
next((vm) => {
console.log('已创建的组件实例:', vm)
})
}
}
该守卫在进入组件前调用。此时组件实例还未创建,所以不能访问 this。它在路由 beforeEnter 后、全局 beforeResolve 前执行;只有这个组件守卫支持通过 next(vm => {}) 在导航确认并创建实例后取得实例。
beforeRouteUpdate
组件已被复用但路由参数等发生变化时调用,实例已经存在:
export default {
beforeRouteUpdate(to, from) {
console.log('beforeRouteUpdate', to.params)
}
}
beforeRouteLeave
离开当前路由组件时调用,常用于阻止用户意外离开未保存页面:
export default {
beforeRouteLeave() {
if (hasUnsavedChanges) return false
}
}
Composition API
Router 4 提供 onBeforeRouteUpdate 和 onBeforeRouteLeave,可在任何由 <router-view> 渲染的组件中使用:
onBeforeRouteUpdate((to) => {
console.log(to.params.id)
})
onBeforeRouteLeave(() => {
if (hasUnsavedChanges.value) return false
})
它们没有 this。Router 4 没有 onBeforeRouteEnter;需要进入前守卫时使用路由配置、全局守卫或 Options API 的 beforeRouteEnter。
完整的导航解析流程
以下是一次成功导航的标准解析流程;失败导航会提前结束,但 afterEach 仍可通过 failure 观察结果。原文 12 步流程与官方顺序一致,完整保留如下:
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave守卫。 - 调用全局的
beforeEach守卫。 - 在重用的组件里调用
beforeRouteUpdate守卫。 - 在路由配置里调用
beforeEnter。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter。 - 调用全局的
beforeResolve守卫。 - 导航被确认。
- 调用全局的
afterEach钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter守卫中传给next的回调,创建好的组件实例会作为回调参数传入。
这张历史流程图按单一组件导航做了简化。嵌套路由中,复用的父组件和新激活的子组件可能同时存在,不能把两条分支理解为互斥;请以旁边的线性步骤为准。

