路由元数据

路由元数据(meta)允许你为每条路由附加自定义数据,例如页面标题、权限要求、缓存策略等。这些数据不会影响路由的匹配逻辑,但可以在导航守卫、组件或其他地方读取和使用,是实现路由级别业务逻辑的关键机制。

定义元数据

通过 meta 字段为路由定义元数据:

ts
const routes = defineRoutes(
  {
    path: '/admin',
    component: AdminPanel,
    meta: {
      requiresAuth: true,
      title: '管理后台'
    }
  },
  {
    path: '/home',
    component: Home,
    meta: {
      requiresAuth: false,
      title: '首页'
    }
  }
)

访问元数据

在组件中通过 useRoute() 获取当前路由的元数据:

tsx
import { useRoute } from 'vitarx-router'

function AdminPanel() {
  const route = useRoute()
  return <h1>{route.meta.title}</h1> // "管理后台"
}

元数据是响应式的

route.meta 具有响应性,当导航到不同路由时,meta 会自动更新:

tsx
import { watch } from 'vitarx'
import { useRoute } from 'vitarx-router'

function App() {
  const route = useRoute()

  // 监听 meta 变化,动态更新页面标题
  watch(
    () => route.meta.title,
    title => {
      document.title = title || '我的应用'
    }
  )

  return <RouterView />
}

元数据合并

当路由存在嵌套关系时,子路由会继承父路由的 meta,并在同名属性上进行覆盖:

ts
const routes = defineRoutes({
  path: '/admin',
  meta: { requiresAuth: true, layout: 'admin' },
  children: [
    {
      path: 'dashboard',
      meta: { title: '仪表盘' }
      // 最终 meta: { requiresAuth: true, layout: 'admin', title: '仪表盘' }
    },
    {
      path: 'settings',
      meta: { title: '设置', layout: 'simple' }
      // 最终 meta: { requiresAuth: true, layout: 'simple', title: '设置' }
    }
  ]
})

合并规则:

  • 父路由的 meta 作为基础
  • 子路由的 meta 浅合并到父路由的 meta
  • 子路由的同名属性覆盖父路由

INFO

合并是浅层的(一层展开),不会递归合并嵌套对象。如果 meta 中包含对象类型的属性,子路由的定义会完全替换父路由的同名属性。

TypeScript 扩展

通过 TypeScript 的模块扩展可以为 meta 添加自定义字段的类型声明,获得完整的代码提示和类型检查。详见 TypeScript 支持

实际应用

元数据最常见的应用场景是与导航守卫配合,实现权限控制:

ts
import { createRouter, defineRoutes } from 'vitarx-router'

const routes = defineRoutes(
  { path: '/', component: Home, meta: { requiresAuth: false, title: '首页' } },
  { path: '/login', component: Login, meta: { requiresAuth: false, title: '登录' } },
  { path: '/admin', component: Admin, meta: { requiresAuth: true, title: '管理后台' } },
  { path: '/profile', component: Profile, meta: { requiresAuth: true, title: '个人中心' } }
)

const router = createRouter({
  routes,
  beforeEach(to, from) {
    // 检查目标路由是否需要认证
    if (to.meta.requiresAuth && !isLoggedIn()) {
      // 未登录,重定向到登录页
      return {
        index: '/login',
        query: { redirect: to.path }
      }
    }
  },
  afterEach(to) {
    // 更新页面标题
    if (to.meta.title) {
      document.title = to.meta.title
    }
  }
})

function isLoggedIn(): boolean {
  // 检查用户是否已登录
  return !!localStorage.getItem('token')
}

引用:关于导航守卫的详细用法,请参阅全局守卫

完整示例

tsx
import { createRouter, defineRoutes, RouterView, RouterLink, useRoute, watch } from 'vitarx-router'
import { createApp } from 'vitarx'

function Home() {
  return <h2>首页</h2>
}

function Login() {
  return <h2>登录页</h2>
}

function Admin() {
  return <h2>管理后台(需要认证)</h2>
}

function Profile() {
  const route = useRoute()
  return <h2>个人中心 — {route.meta.title}</h2>
}

const routes = defineRoutes(
  { path: '/', component: Home, meta: { requiresAuth: false, title: '首页' } },
  { path: '/login', component: Login, meta: { requiresAuth: false, title: '登录' } },
  { path: '/admin', component: Admin, meta: { requiresAuth: true, title: '管理后台' } },
  { path: '/profile', component: Profile, meta: { requiresAuth: true, title: '个人中心' } }
)

const router = createRouter({
  routes,
  beforeEach(to) {
    if (to.meta.requiresAuth && !localStorage.getItem('token')) {
      return { index: '/login', query: { redirect: to.path } }
    }
  },
  afterEach(to) {
    document.title = to.meta.title || '我的应用'
  }
})

function App() {
  return (
    <div>
      <nav>
        <RouterLink to="/">首页</RouterLink>
        <RouterLink to="/admin">管理后台</RouterLink>
        <RouterLink to="/profile">个人中心</RouterLink>
      </nav>
      <RouterView />
    </div>
  )
}

createApp(App).use(router).mount('#app')