vue-router hooks怎么用?从基础到实战一次讲透
咱做Vue项目时,路由跳转的控制、权限验证、数据预加载这些需求,基本都得靠vue-router hooks(导航守卫)来实现,但刚接触时,很多同学会疑惑:这些钩子分哪几类?怎么在不同场景下用?执行顺序容易搞混咋办?今天就用问答形式,把vue-router hooks从基础到实战的知识点拆明白,解决你开发里的实际问题。
先搞懂:vue-router hooks是啥?不同类型有啥区别?
vue-router hooks 叫“导航守卫”,作用是在路由跳转的不同阶段插入逻辑,跳转前验证权限”“进入组件前加载数据”“离开页面时提示保存”,按作用范围和触发时机,分成三类:
- 全局钩子:作用于整个路由系统,所有路由跳转都会触发,像
router.beforeEach这种,适合处理“登录验证”“全局埋点”这类项目级需求。 - 路由独享钩子:只对单个路由生效,写在路由配置里(
beforeEnter),适合“某路由专属的权限验证”“单个页面的数据预加载”。 - 组件内钩子:写在
.vue组件的选项里(beforeRouteEnter),和组件生命周期深度绑定,能直接操作组件实例,适合“组件自身的路由交互”(比如表单离开提示、根据路由参数更新数据)。
举个栗子:全局钩子像小区大门的保安,所有住户进出都要查;路由独享像某栋楼的门禁,只拦这栋楼的人;组件内钩子像你家的门,只管你自己进出时的操作~
全局钩子怎么用?能解决哪些实际问题?
全局钩子主要有 beforeEach(跳转前拦截)、beforeResolve(组件解析后拦截)、afterEach(跳转后操作)这几个,每个钩子的场景和用法都很明确:
(1)beforeEach:“跳转前的第一道拦截”
导航触发后,所有其他钩子执行前,它先拦下来,最经典的场景是登录态验证。
// router/index.js
router.beforeEach((to, from, next) => {
const isLogin = localStorage.getItem('token') // 假设token存登录状态
// 路由元信息meta里,给需要登录的页面加requiresAuth标记
if (to.meta.requiresAuth && !isLogin) {
next('/login') // 没登录?先跳登录页
} else {
next() // 正常放行
}
})
路由配置里给需要权限的页面加标记:
{
path: '/profile',
component: Profile,
meta: { requiresAuth: true }
}
除了登录,还能做国际化切换(跳转前改语言)、广告拦截(某些页面强制跳广告页)这些全局逻辑。
(2)beforeResolve:“跳转前的最后一道拦截”
和 beforeEach 逻辑类似,但它会等异步路由组件加载完再执行,适合“确保组件准备好后,再做最后判断”。
router.beforeResolve((to, from, next) => {
// 假设某些页面需要加载动态配置后才能进
if (to.meta.needConfig && !window.globalConfig) {
fetchConfig().then(() => next()) // 加载配置后放行
} else {
next()
}
})
(3)afterEach:“跳转后的后置操作”
导航完成后执行,不影响跳转流程,适合做“埋点统计”“关闭加载动画”,比如配合NProgress进度条:
import NProgress from 'nprogress'
router.beforeEach(() => { NProgress.start() }) // 跳转前启动进度条
router.afterEach(() => { NProgress.done() }) // 跳转后结束进度条
路由独享钩子(beforeEnter)适合哪些场景?
路由独享钩子写在单个路由的配置项里,作用范围更“精准”,适合两类场景:
(1)单路由的额外权限验证
比如后台管理页,全局验证登录后,还要确认是管理员,路由配置里加 beforeEnter:
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
const role = localStorage.getItem('role')
if (role !== 'admin') {
next('/403') // 不是管理员?跳权限不足页
} else {
next()
}
}
}
(2)单路由的数据预加载
比如活动页需要先拉取配置,再渲染组件。beforeEnter 里发请求:
{
path: '/activity',
component: Activity,
beforeEnter: async (to, from, next) => {
const config = await fetchActivityConfig()
to.meta.config = config // 把配置存在路由元信息,组件里能拿到
next()
}
}
组件里直接用 this.$route.meta.config 取数据,不用等组件创建后再请求~
组件内的钩子有啥独特优势?怎么结合组件生命周期用?
组件内钩子写在 .vue 组件的 export default 里,有 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave 三个,优势是和组件自身状态深度绑定,能直接操作组件实例(除了 beforeRouteEnter 早期阶段)。
(1)beforeRouteEnter:组件创建前触发(适合“预加载数据”)
因为组件还没实例化,所以this是undefined,但可以通过 next 的回调访问组件实例,比如进入文章详情页,先拉取文章数据:
export default {
data() { return { article: {} } },
beforeRouteEnter(to, from, next) {
axios.get(`/api/article/${to.params.id}`)
.then(res => {
// next的回调里,vm是组件实例
next(vm => {
vm.article = res.data
})
})
}
}
这样组件渲染时,数据已经准备好了,不会出现“先渲染空页面,再加载数据”的闪烁问题~
(2)beforeRouteUpdate:路由参数变化时触发(适合“组件复用”)
比如路由是 /user/:id,当 id 从1变2,组件实例会被复用(避免重复创建),这时候用 beforeRouteUpdate 处理参数变化:
export default {
methods: {
fetchUser(id) { /* 发请求更新用户数据 */ }
},
beforeRouteUpdate(to, from, next) {
this.fetchUser(to.params.id) // 根据新id重新请求数据
next()
}
}
(3)beforeRouteLeave:离开组件时触发(适合“表单未保存提示”)
用户编辑表单时,没保存就离开?用这个钩子弹提示:
export default {
data() { return { formModified: false } },
beforeRouteLeave(to, from, next) {
if (this.formModified) { // 表单有修改
const confirm = window.confirm('表单还没保存,确定离开?')
if (confirm) next() // 确认就走
else next(false) // 取消就留在当前页
} else {
next() // 没修改,直接走
}
}
}
钩子的执行顺序是怎样的?实际开发容易踩哪些坑?
很多同学搞不清多个钩子同时存在时的执行顺序,导致逻辑冲突或导航卡住,先把顺序理清楚(关键步骤):
- 导航被触发(比如点击
<router-link>)→ - 调用离开组件的beforeRouteLeave(如果当前组件有这个钩子)→
- 调用全局的beforeEach→
- 调用路由配置里的beforeEnter→
- 解析异步路由组件(如果有)→
- 调用进入组件的beforeRouteEnter→
- 调用全局的beforeResolve→
- 导航确认,更新URL→
- 调用全局的afterEach→
- 执行beforeRouteEnter的next回调(此时组件实例已创建)。
容易踩的坑:
-
next没调用,导航卡住:每个钩子都要确保调用
next()、next(false)或next('/path'),比如写了if判断,但分支里忘了写next,路由就会一直“卡着”,页面没反应。 -
beforeRouteEnter里this为undefined:因为组件还没创建,直接访问
this.xxx会报错,必须用next(vm => { vm.xxx = ... })来操作组件实例。 -
异步组件加载时的钩子顺序:如果路由用了
component: () => import('./xxx.vue')这种异步加载,beforeEnter和beforeRouteEnter要等组件加载完才执行,处理异步逻辑时,要注意时机。 -
全局和组件内钩子逻辑冲突:比如全局
beforeEach里跳登录页,组件内beforeRouteEnter又想跳其他页,要注意执行顺序,避免循环跳转。
怎么用hooks实现权限控制?给个完整案例!
权限控制是hooks最常见的场景,一般分“登录态验证”和“角色权限验证”,结合全局钩子+路由元信息,步骤如下:
步骤1:配置路由元信息(meta)
给需要权限的路由加标记,requiresAuth(是否需要登录)、role(需要的角色):
const routes = [
{ path: '/', component: Home },
{ path: '/login', component: Login },
{
path: '/profile',
component: Profile,
meta: { requiresAuth: true } // 需登录
},
{
path: '/admin',
component: Admin,
meta: { requiresAuth: true, role: 'admin' } // 需登录+管理员角色
},
{ path: '/403', component: Forbidden } // 权限不足页
]
步骤2:全局beforeEach处理权限
在 router/index.js 里写全局拦截:
router.beforeEach((to, from, next) => {
const isLogin = localStorage.getItem('token') // 假设token存登录态
const userRole = localStorage.getItem('role') // 假设role存用户角色
// 1. 处理“需要登录”的路由
if (to.meta.requiresAuth) {
if (!isLogin) {
next('/login') // 没登录?先跳登录页
return
}
// 2. 处理“角色权限”(如果有role要求)
if (to.meta.role) {
if (userRole !== to.meta.role) {
next('/403') // 角色不符?跳权限不足页
return
}
}
}
next() // 所有验证通过,放行
})
步骤3:处理登录逻辑(Login组件)
用户登录后,保存 token 和 role 到 localStorage,再跳转到目标页:
export default {
data() { return { username: '', password: '' } },
methods: {
login() {
axios.post('/api/login', {
username: this.username,
password: this.password
}).then(res => {
localStorage.setItem('token', res.data.token)
localStorage.setItem('role', res.data.role)
this.$router.push('/profile') // 跳转到需要权限的页面
})
}
}
}
这样整个权限系统就串联起来了:全局钩子拦截所有需要权限的路由,路由元信息灵活配置权限要求,登录组件处理状态保存,实际项目中还能扩展(比如多角色、动态路由),但核心逻辑都是这么玩的~
数据预加载场景下,hooks怎么配合异步请求?
数据预加载指“进入路由前就把数据准备好,避免组件渲染后再请求导致的闪烁”,hooks在这场景下有三种思路,各有适用场景:
方案1:组件内钩子(beforeRouteEnter)
适合组件专属数据的预加载,比如文章详情页:
export default {
data() { return { article: {} } },
beforeRouteEnter(to, from, next) {
const articleId = to.params.id
fetchArticle(articleId)
.then(res => {
// next的回调传入数据给组件实例
next(vm => {
vm.article = res.data
})
})
.catch(err => {
console.error('获取文章失败', err)
next('/404') // 失败跳404
})
}
}
方案2:路由独享钩子(beforeEnter)
适合单个路由的复杂逻辑,或多个组件复用同一套请求逻辑。
{
path: '/article/:id',
component: Article,
beforeEnter: async (to, from, next) => {
try {
const res = await fetchArticle(to.params.id)
to.meta.article = res.data // 把数据存在路由元信息
next()
} catch (err) {
next('/404')
}
}
}
// Article组件里:
export default {
computed: {
article() {
return this.$route.meta.article
}
}
}
方案3:全局beforeResolve
适合多路由通用的加载逻辑,比如所有带 dataLoader 的路由,自动执行加载:
router.beforeResolve((to, from, next) => {
if (to.meta.dataLoader) {
// dataLoader是路由元信息里的异步函数,返回数据
to.meta.dataLoader(to)
.then(() => next())
.catch(() => next('/404'))
} else {
next()
}
})
// 路由配置:
{
path: '/product/:id',
component: Product,
meta: {
dataLoader: (to) => fetchProduct(to.params.id).then(res => {
to.meta.product = res.data
})
}
}
核心逻辑是利用钩子在“路由导航完成前”把数据准备好,让组件渲染时直接用数据,避免闪烁~
路由切换时的动画和加载状态,hooks怎么处理?
路由切换时的过渡动画、加载进度条是提升体验的细节,hooks能精准控制“时机”:
(1)配合Vue过渡动画
用 beforeRouteLeave 控制“离开动画完成后再跳转”。
export default {
data() { return { leaveAnimation: false } },
beforeRouteLeave(to, from, next) {
this.leaveAnimation = true // 触发离开动画的class
// 监听动画结束事件(假设用了CSS过渡)
this.$el.addEventListener('animationend', () => {
next() // 动画结束后,再跳转
}, { once: true }) // 只监听一次
}
}
(2)加载进度条(如NProgress)
用全局 beforeEach 和 afterEach 控制进度条的“开始”和“结束”:
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
router.beforeEach(() => { NProgress.start() }) // 导航开始,启动进度条
router.afterEach(() => { NProgress.done() }) // 导航完成,结束进度条
如果有异步组件或数据预加载,进度条可能“瞬间完成”(因为钩子执行快,实际组件还在加载),这时候用 beforeResolve 加延迟:
router.beforeResolve((to, from, next) => {
setTimeout(() => {
NProgress.inc() // 增加进度,模拟加载中
next()
}, 300)
})
(3)页面加载状态提示
在组件内用 beforeRouteEnter 显示“加载中”,数据获取后隐藏:
export default {
data() { return { isLoading: true } },
beforeRouteEnter(to, from, next) {
fetchData().then(() => {
next(vm => {
vm.isLoading = false
})
})
}
}
// 模板中:
<template>
<div>
<div v-if="isLoading">加载中...</div>
<div v-else>{{ data }}</div>
</div>
</template>
这些细节让路由切换更丝滑,hooks的核心价值就是“时机控制”——知道什么时候开始动画、什么时候结束加载,让用户感知到流程的连贯性~
vue-router hooks的核心是“流程控制”
不管是全局、路由独享
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



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