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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。