一、matched属性到底是什么?
不少用Vue做项目开发的同学,在处理路由逻辑时,大概率会碰到Vue Router里的matched
属性,这东西到底是干啥的?啥场景能用上?和route
里其他属性咋区分?今天从基础概念到实际项目案例,把matched
属性拆明白,帮你在路由处理上少踩坑。
先从路由匹配的过程说起,Vue Router的核心是“匹配路径,渲染对应组件”,但实际项目里经常有嵌套路由(比如页面分布局壳组件+子页面组件),当你访问一个带嵌套结构的路径时,Vue Router会把“父路由记录”和“子路由记录”都找出来,这些记录组成的数组,就是matched
。
举个例子,路由配置长这样:
const routes = [ { path: '/user', name: 'User', component: UserLayout, // 父组件,比如包含侧边栏、头部 children: [ { path: 'profile', name: 'UserProfile', component: UserProfile // 子组件,用户资料页面 }, { path: 'settings', name: 'UserSettings', component: UserSettings // 子组件,设置页面 } ] } ]
当你在浏览器访问/user/profile
时,Vue Router会先匹配到父路由/user
,再匹配到子路由/user/profile
,这时候,$route.matched
这个数组里就会有两个元素:第一个是/user
的路由记录,第二个是/user/profile
的路由记录,数组的顺序是从父到子依次排列的。
简单说,matched
当前路径匹配到的所有路由记录的集合”,不管是父路由还是嵌套的子路由,只要路径匹配上了,就会被放进这个数组里。
为什么项目里需要matched属性?
光知道概念没用,得搞清楚“啥时候非得用它”,实际开发中,这三个场景几乎离不开matched
:
面包屑导航自动生成
面包屑是典型的“层级导航”,首页 > 文章列表 > 文章详情”,每个层级对应一个路由,而路由的层级关系正好保存在matched
里。
假设每个路由的meta
字段存了页面标题,像这样配置:
const routes = [ { path: '/home', name: 'Home', component: Home, meta: { title: '首页' }, children: [ { path: 'article', name: 'ArticleList', component: ArticleList, meta: { title: '文章列表' }, children: [ { path: ':id', name: 'ArticleDetail', component: ArticleDetail, meta: { title: '文章详情' } } ] } ] } ]
然后写个面包屑组件,遍历$route.matched
生成导航:
<template> <div class="breadcrumb"> <span v-for="(routeRecord, index) in $route.matched" :key="index" > <!-- 跳转到对应路由的路径 --> <router-link :to="routeRecord.path"> {{ routeRecord.meta.title }} </router-link> <!-- 最后一个层级不加分隔符 --> <span v-if="index !== $route.matched.length - 1"> / </span> </span> </div> </template>
当你访问/home/article/123
时,面包屑会自动变成“首页 / 文章列表 / 文章详情”——完全不用手动维护层级关系,matched
帮你把父路由和子路由的信息全捞出来了。
路由权限的细粒度控制
很多后台系统需要“不同页面、不同按钮权限”,这时候得检查每个层级路由的权限要求,比如父路由要求“管理员”才能进,子路由要求“超级管理员”,这时候就得遍历matched
里的所有路由记录,确保每个都满足权限。
举个全局路由守卫的例子(控制页面级权限):
router.beforeEach((to, from, next) => { // 第一步:判断是否需要登录 const needLogin = to.matched.some(record => record.meta.needLogin) // 假设用localStorage存用户登录状态 const isLogin = !!localStorage.getItem('token') <p>if (needLogin) { if (!isLogin) { // 没登录就跳登录页 next({ name: 'Login' }) return } // 第二步:判断角色权限(比如只有admin能进) const userRole = localStorage.getItem('role') // 假设存了角色 const needAdmin = to.matched.every(record => { // 路由meta里的role要求,没有要求则默认允许 return !record.meta.role || record.meta.role.includes(userRole) }) if (needAdmin) { next() } else { // 权限不够跳403 next({ name: 'Forbidden' }) } } else { next() } })
这里用了some
和every
两个数组方法:some
表示“只要有一个路由要求登录,就需要验证”;every
表示“所有路由都要求角色符合,才算有权限”,如果不用matched
,只检查当前路由(to.name
或to.path
),就会漏掉父路由的权限要求——比如父路由要求登录,子路由没写meta
,这时候不检查父路由就会有安全漏洞。
动态设置页面标题(SEO & 体验优化)
document.title
)是SEO和用户体验的关键,嵌套路由下,子页面的标题往往更具体(文章详情 - 我的博客”),这时候可以用matched
的最后一个元素(最内层的子路由)来设置标题。
在路由的全局后置守卫里写逻辑:
router.afterEach((to) => { // 取matched数组的最后一个元素(最内层路由) const lastRoute = to.matched[to.matched.length - 1] // 如果有meta.title就用,没有就用默认 document.title = lastRoute?.meta.title || '我的网站 - 首页' })
这样不管是访问父路由还是子路由,标题都会自动更新,比如访问/user/profile
时,用UserProfile
路由的meta.title
;访问/user
时,用User
路由的meta.title
。
matched和route其他属性咋区分?
刚接触Vue Router时,很容易把$route.path
、$route.name
和$route.matched
搞混,这里直接对比着讲:
属性 | 含义 | 特点 |
---|---|---|
$route.path |
当前路径的字符串(如/user/1 ) |
只反映“最终路径”,没有层级信息 |
$route.name |
当前路由的命名(如UserDetail ) |
只反映“当前路由”的名字,父路由名拿不到 |
$route.matched |
匹配到的路由记录数组 | 包含所有层级的路由记录(父→子) |
举个具体的例子,路由配置还是之前的嵌套结构:
const routes = [ { path: '/user', name: 'User', component: UserLayout, children: [ { path: ':id', name: 'UserDetail', component: UserDetail } ] } ]
当访问/user/123
时:
$route.path
是"/user/123"
(字符串,只有最终路径)$route.name
是"UserDetail"
(只有当前子路由的名字)$route.matched
是[ { User路由记录 }, { UserDetail路由记录 } ]
(两个路由记录,父和子)
如果你需要“层级信息”(比如父路由的meta
、父路由的name
),必须用matched
;如果只关心当前路由的路径或名字,用path
、name
就够了。
实战中怎么用matched解决问题?
光讲理论不够,结合三个真实场景,看matched
怎么落地:
场景1:带图标和分隔符的面包屑
很多后台系统的面包屑不仅要文字,还要图标和自定义分隔符,这时候可以在路由的meta
里加icon
字段,然后在组件里渲染:
路由配置(加icon
):
const routes = [ { path: '/order', name: 'Order', component: OrderLayout, meta: { title: '订单管理', icon: 'icon-order' }, children: [ { path: 'list', name: 'OrderList', component: OrderList, meta: { title: '订单列表', icon: 'icon-order-list' } } ] } ]
面包屑组件(加图标):
<template> <div class="breadcrumb"> <div v-for="(record, index) in $route.matched" :key="index" class="breadcrumb-item" > <!-- 渲染图标 --> <i :class="record.meta.icon"></i> <!-- 文字和链接 --> <router-link :to="record.path">{{ record.meta.title }}</router-link> <!-- 分隔符,最后一个不加 --> <span v-if="index !== $route.matched.length - 1"> → </span> </div> </div> </template> <p><style scoped> .breadcrumb { display: flex; align-items: center; } .breadcrumb-item { display: flex; align-items: center; } .breadcrumb-item i { margin-right: 4px; } </style>
这样生成的面包屑既有图标又有自定义分隔符,层级关系全靠matched
自动处理。
场景2:嵌套路由的缓存与激活
在Vue的<keep-alive>
组件中,有时候需要根据路由层级决定是否缓存,比如父路由的组件要缓存,子路由的组件不缓存,这时候可以用matched
判断当前路由属于哪一层。
假设需求:只有父路由/dashboard
的组件需要缓存,子路由不需要。
路由配置(给父路由加meta.cache
):
const routes = [ { path: '/dashboard', name: 'Dashboard', component: DashboardLayout, meta: { cache: true }, children: [ { path: 'analysis', name: 'Analysis', component: Analysis } ] } ]
App.vue中控制缓存:
<template> <div id="app"> <router-view v-slot="{ Component }"> <!-- 判断当前路由的父路由是否需要缓存 --> <keep-alive v-if="shouldKeepAlive"> <component :is="Component" /> </keep-alive> <component :is="Component" v-else /> </router-view> </div> </template> <p><script> export default { computed: { shouldKeepAlive() { // 找到父路由(Dashboard)的记录 const parentRecord = this.$route.matched.find( record => record.name === 'Dashboard' ) return parentRecord?.meta.cache || false } } } </script>
这里通过matched
找到父路由的记录,再根据meta.cache
决定是否缓存——如果不用matched
,很难精准找到父路由的配置。
场景3:动态加载路由的进度条
当用路由懒加载(component: () => import('./views/xxx.vue')
)时,页面切换可能有延迟,需要加进度条,这时候可以结合matched
判断“是否在加载嵌套路由”。
思路:在路由守卫中,监听所有matched
路由的组件加载状态,只要有一个在加载,就显示进度条。
简化代码示例:
// 假设用nprogress库做进度条 import NProgress from 'nprogress' import 'nprogress/nprogress.css' <p>router.beforeEach((to, from, next) => { NProgress.start() // 开始进度条 next() })</p> <p>router.afterEach(() => { NProgress.done() // 结束进度条 })</p> <p>// 额外处理:如果路由是懒加载,在组件加载时显示进度 router.onError((error) => { console.error('路由加载失败:', error) NProgress.done() })</p> <p>// 进阶:针对matched里的每个路由,判断是否在加载 // (需要结合webpack的加载状态,这里简化演示) const handleRouteLoad = (to) => { to.matched.forEach(record => { if (record.components.default.loading) { // 假设懒加载的组件有loading状态 NProgress.set(0.5) // 加载到一半时的进度 } }) } router.beforeResolve(handleRouteLoad)
虽然实际项目中判断组件加载状态更复杂,但核心思路是通过matched
遍历所有层级的路由记录,监控每个路由的加载状态——如果不用matched
,只能监控当前路由,
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。