一、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前端网发表,如需转载,请注明页面地址。
code前端网



