Vue Router里的中间件到底指啥?
不少刚接触Vue Router的同学,总会疑惑中间件到底是干啥的、怎么在项目里用起来,其实Vue Router本身没直接提供“中间件”这个官方API,但我们能通过导航守卫等机制,自己实现类似中间件的逻辑,用来处理权限验证、数据预加载这些常见需求,接下来就从基础概念到实战场景,把Vue Router中间件相关的问题拆开来聊~
先类比后端,比如Express里的中间件是请求到达路由前执行的通用逻辑(比如日志、鉴权),Vue Router里的“中间件”是**类比这个概念**——在路由导航的过程中(比如从/home跳转到/profile时),插入一段可复用的逻辑,完成权限检查、数据加载、行为统计这些事。Vue Router本身提供的是「导航守卫」(像全局的beforeEach、路由独享的beforeEnter、组件内的beforeRouteEnter这些钩子),而“中间件”是我们基于这些守卫,把通用逻辑抽成独立函数,让不同路由能共享这些逻辑,举个简单例子:做登录验证时,不需要每个路由的beforeEnter都写一遍“判断token是否存在,不存在跳登录页”,而是把这段逻辑写成一个checkAuth函数,在全局守卫里调用,这就是中间件思维。
为啥要在Vue Router里搞中间件逻辑?
简单说,**为了让路由相关的通用逻辑“复用起来更爽、维护起来更轻”**,具体看几个场景:- 权限控制:必须登录才能进个人中心、订单页”,把“检查登录态”抽成中间件后,所有需要登录的路由都能复用,不用重复写判断代码。
- 数据预加载:进入文章详情页前,得先根据文章ID把内容请求回来,用中间件在路由跳转前加载数据,能避免页面先渲染再加载导致的“空白闪烁”。
- 埋点统计:用户进入每个路由时,要上报“用户进入XX页面”的行为,把埋点逻辑写成中间件,所有路由能统一处理。
- 页面跳转前的“拦截”:比如表单页面没保存就想离开,要弹出“是否确认离开”的提示,中间件能在路由切换前统一处理这类交互。
如果不搞中间件,这些逻辑要么散落在各个路由的守卫里(重复代码一堆),要么塞在组件内的生命周期里(耦合度高,换个路由还要重新写),所以中间件是「把路由级的通用逻辑模块化」的好办法。
怎么自己实现Vue Router中间件?
核心思路是**“用导航守卫承载逻辑,把重复代码抽成函数,再按顺序执行这些函数”**,下面分三种常见实现方式讲:全局中间件:所有路由都要经过的逻辑
全局登录验证”“全局埋点”这类逻辑,适合用全局守卫(router.beforeEach)+ 抽函数的方式。
举个登录验证的例子:
// 第一步:把验证逻辑抽成中间件函数 function checkAuth(to, from, next) { const isLogin = localStorage.getItem('token') // 假设用localStorage存token if (isLogin) { next() // 已登录,继续跳转到目标路由 } else { next('/login') // 没登录,跳登录页 } } // 第二步:在全局守卫里执行中间件 const router = createRouter({ ... }) router.beforeEach((to, from, next) => { checkAuth(to, from, next) // 执行登录验证中间件 })
如果有多个全局中间件(比如还要埋点、还要设置页面标题),可以把它们放进数组,用递归或循环执行:
const globalMiddlewares = [checkAuth, setPageTitle, trackPageView] router.beforeEach((to, from, next) => { let currentIndex = 0 // 记录当前执行到第几个中间件 function runNextMiddleware() { if (currentIndex >= globalMiddlewares.length) { return next() // 所有中间件执行完,继续导航 } const currentMiddleware = globalMiddlewares[currentIndex] currentIndex++ currentMiddleware(to, from, runNextMiddleware) // 执行当前中间件,完成后执行下一个 } runNextMiddleware() })
路由独享中间件:只给特定路由用的逻辑
有些逻辑不是全局的,进入管理员页面需要管理员权限”“进入文章详情页要预加载文章数据”,这类路由专属的逻辑,适合用「路由配置的meta字段 + 全局守卫判断」来实现。
步骤:
- 在路由的meta里配置中间件列表
- 全局守卫里读取meta,执行对应的中间件
举个“文章详情页预加载数据”的例子:
// 第一步:抽数据预加载中间件 async function loadArticle(to, from, next) { const articleId = to.params.id const article = await api.getArticle(articleId) // 假设api是请求工具 store.commit('setCurrentArticle', article) // 把数据存到Vuex next() // 数据加载完,继续导航 } // 第二步:在路由配置的meta里指定中间件 const routes = [ { path: '/article/:id', component: ArticleDetail, meta: { middlewares: [loadArticle] // 这个路由要执行的中间件 } } ] // 第三步:全局守卫里处理路由独享中间件 router.beforeEach((to, from, next) => { const routeMiddlewares = to.meta.middlewares || [] // 取出当前路由的中间件 const globalMiddlewares = [checkAuth, setPageTitle] // 全局中间件 const allMiddlewares = [...globalMiddlewares, ...routeMiddlewares] // 合并 let currentIndex = 0 function runMiddleware() { if (currentIndex >= allMiddlewares.length) return next() const middleware = allMiddlewares[currentIndex] currentIndex++ middleware(to, from, runMiddleware) } runMiddleware() })
组件内中间件?(不推荐,但得了解)
组件内的导航守卫(比如beforeRouteEnter)也能写逻辑,但复用性极差——每个组件都要写一遍,完全违背中间件“复用”的初衷,所以除非是组件专属、完全没法通用的逻辑,否则优先用全局或路由级的中间件。
中间件和Vue Router导航守卫有啥区别?
很多同学会把这俩搞混,其实是「机制」和「逻辑组织方式」的区别:- 导航守卫是Vue Router提供的底层钩子(比如beforeEach、beforeEnter、beforeRouteEnter),决定了“在路由导航的哪个阶段执行代码”(比如跳转前、跳转后、进入组件前)。
- 中间件是基于导航守卫的上层逻辑封装——把通用的路由逻辑(权限、数据、埋点等)抽成可复用的函数,再通过导航守卫去执行这些函数。
举个通俗例子:导航守卫是“舞台上的不同表演时段”(比如开场前、表演中、谢幕后),中间件是“在某个时段要表演的节目”(比如开场前要检查门票、发纪念品),节目(中间件)得靠时段(守卫)来承载,但节目可以复用,时段是固定的机制。
实际项目里,中间件能解决哪些典型问题?
光讲概念太虚,结合具体场景看更清楚:场景1:多级权限验证(普通用户、管理员、超级管理员)
不同路由需要不同角色才能进入,
- /admin 只有管理员能进
- /super-admin 只有超级管理员能进
- /user 普通用户和管理员都能进
用中间件实现“按角色拦截”:
// 抽一个通用的角色检查中间件(支持传参) function checkRole(requiredRole) { return (to, from, next) => { const userRole = store.state.user.role // 假设从Vuex取角色 if (userRole === requiredRole) { next() } else { next('/403') // 无权限页面 } } } // 路由配置里按需使用 const routes = [ { path: '/admin', component: AdminPage, meta: { middlewares: [checkRole('admin')] // 传参指定需要admin角色 } }, { path: '/super-admin', component: SuperAdminPage, meta: { middlewares: [checkRole('super-admin')] } } ]
场景2:数据预加载(让页面打开即有数据)
比如进入商品详情页/product/:id
前,先把商品数据请求好,避免页面先渲染再加载导致的“空白”。
中间件实现:
async function loadProduct(to, from, next) { const productId = to.params.id try { const product = await api.getProduct(productId) store.commit('setCurrentProduct', product) // 存到Vuex next() // 数据加载完,继续跳转 } catch (error) { next('/404') // 请求失败跳404 } } // 路由配置 { path: '/product/:id', component: ProductDetail, meta: { middlewares: [loadProduct] } }
场景3:页面跳转前的“拦截提示”(防止用户误操作)
比如用户在填写表单的页面(/form
),表单内容没保存就想跳走,要弹出确认提示。
中间件实现:
function confirmLeave(to, from, next) { const isFormDirty = store.state.form.isDirty // 假设表单脏状态存在Vuex if (isFormDirty) { const isConfirm = window.confirm('表单还没保存,确定要离开吗?') if (isConfirm) { next() // 确认离开,继续跳转 } else { next(false) // 取消跳转,留在当前页 } } else { next() // 表单没修改,直接跳转 } } // 路由配置(给/form路由加中间件) { path: '/form', component: FormPage, meta: { middlewares: [confirmLeave] } }
场景4:多语言切换同步(路由和i18n联动)
假设路由是/en/about
/zh/about
这种格式,切换路由时要同步修改i18n的语言。
中间件实现:
function setLocale(to, from, next) { const lang = to.path.split('/')[1] || 'zh' // 从路由里取语言段,默认中文 i18n.locale = lang // 假设i18n是VueI18n实例 next() } // 全局中间件(所有路由都要执行) const globalMiddlewares = [setLocale] router.beforeEach((to, from, next) => { // 执行全局中间件... })
写Vue Router中间件容易踩哪些坑?怎么避?
自己实现中间件时,几个高频踩坑点得注意:坑1:中间件执行顺序乱了
先执行权限验证,再执行数据预加载”,结果代码里顺序写反了,导致数据还没加载完就开始权限验证(可能拿到空数据)。
解决:
- 全局中间件和路由独享中间件的合并顺序要明确(比如先全局、后路由独享)。
- 用数组存中间件,通过索引递增的方式顺序执行(像前面例子里的
currentIndex++
+ 递归调用nextMiddleware)。
坑2:next()调用不当导致死循环
最典型的是“权限验证中间件”和“登录页路由”的循环:用户没登录时,中间件跳转到/login;但/login路由也需要权限验证(又触发中间件),导致无限循环跳转。
解决:
在路由的meta里加“忽略验证”的标记,
// 登录页路由配置 { path: '/login', component: LoginPage, meta: { ignoreAuth: true // 表示这个路由不需要权限验证 } } // 权限验证中间件里判断 function checkAuth(to, from, next) { if (to.meta.ignoreAuth) { // 如果当前路由要忽略验证,直接放行 next() return } // 正常验证逻辑... }
坑3:异步中间件没等结果就执行next()
比如数据预加载是异步请求,但中间件里没等请求完成就调用next(),导致页面跳转后数据还没回来,页面渲染空白。
解决:
用async/await或Promise处理异步,确保数据加载完再放行:
async function loadData(to, from, next) { await fetchData() // 等待请求完成 next() // 数据加载完再跳转 }
坑4:中间件逻辑重复,没真正复用
比如每个需要权限的路由,都单独写了“判断token”的代码,没有抽成通用函数。
解决:
把重复逻辑抽成带参数的函数(像前面的checkRole中间件),通过传参来适配不同场景,而不是复制粘贴代码。
有没有更优雅的中间件实现方案?
如果项目里中间件很多,纯手动写递归、合并数组会有点繁琐,可以封装一个**Vue Router中间件插件**,让代码更模块化。思路:写一个插件函数,接收全局中间件列表,然后在插件内部处理“全局+路由独享”中间件的执行逻辑。
示例代码:
// 封装中间件插件 function createMiddlewarePlugin(globalMiddlewares = []) { return function (router) { router.beforeEach((to, from, next) => { // 1. 取出当前路由的独享中间件 const routeMiddlewares = to.meta.middlewares || [] // 2. 合并全局和路由独享中间件 const allMiddlewares = [...globalMiddlewares, ...routeMiddlewares] let currentIndex = 0 // 记录当前执行到第几个中间件 // 递归执行中间件 function runNextMiddleware() { if (currentIndex >= allMiddlewares.length) { return next() // 所有中间件执行完,继续导航 } const currentMiddleware = allMiddlewares[currentIndex] currentIndex++ currentMiddleware(to, from, runNextMiddleware) // 执行当前中间件,完成后执行下一个 } runNextMiddleware() }) } } // 使用插件 const globalMiddlewares = [checkAuth, setLocale] // 全局中间件列表 const middlewarePlugin = createMiddlewarePlugin(globalMiddlewares) const router = createRouter({ history: createWebHistory(), routes: [...] }) router.use(middlewarePlugin) // 注册中间件插件
这样一来,全局中间件和路由独享中间件的逻辑被插件封装了,路由配置和守卫代码更简洁,后续新增中间件也只需要往globalMiddlewares数组里加,或者在路由meta里配,维护成本低很多。
Vue Router中间件的核心逻辑
绕了这么多例子和场景,其实Vue Router中间件的本质很简单——**把路由导航过程中要做的通用逻辑,拆成一个个可复用的函数(中间件),再通过导航守卫按顺序执行这些函数**。不管是简单的登录拦截,还是复杂的“权限验证+数据预载+埋点统计”组合拳,只要记住「逻辑抽成函数、守卫承载执行、顺序控制好、异步处理稳」这几个点,就能把中间件玩得很溜。
要是你刚开始没思路,建议先从单一功能的中间件(比如只做登录验证)写起,慢慢往多中间件组合、插件化封装方向扩展,踩过“执行顺序乱了”“死循环跳转”这些坑后,自然就摸清中间件的脾气了~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。