Code前端首页关于Code前端联系我们

一、matched属性到底是什么?

terry 12小时前 阅读数 16 #Vue

不少用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()
}
})

这里用了someevery两个数组方法:some表示“只要有一个路由要求登录,就需要验证”;every表示“所有路由都要求角色符合,才算有权限”,如果不用matched,只检查当前路由(to.nameto.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;如果只关心当前路由的路径或名字,用pathname就够了。

实战中怎么用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前端网发表,如需转载,请注明页面地址。

发表评论:

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

热门