让路由与组件解耦,写更优雅的 Vue 代码
为什么需要 Router props?
先看一段"经典"代码:
vue
<template>
<div>用户ID:{{ $route.params.id }}</div>
</template>
这有什么问题?组件强依赖 $route,导致:
- 🔴 组件难以测试------必须模拟路由对象
- 🔴 组件难以复用 ------换个场景就得提供
$route - 🔴 代码不够直观------参数传递链路不清晰
Router props 就是来解决这个痛点的------它让路由参数以组件 props 的形式注入,组件完全感知不到路由的存在。
Vue Router 3(Vue2)中的 props 配置
Vue Router 3.x 在路由声明中添加了 props 字段,支持 3 种模式。
布尔模式 ------ props: true
将 $route.params 自动映射为组件 props,简单粗暴:
js
// router/index.js
const routes = [
{
path: '/user/:id',
component: User,
props: true // ✅ 将 params.id 映射为 props.id
}
]
vue
<!-- User.vue -->
<template>
<div>用户ID:{{ id }}</div>
</template>
<script>
export default {
props: ['id']
}
</script>
📌 注意:只映射
params,不会 映射query
对象模式 ------ props: { ... }
传递静态数据,与路由参数无关:
js
{
path: '/promotion',
component: Promotion,
props: { banner: 'summer_sale.jpg' }
}
适用场景: 配置化组件、A/B 测试、功能开关控制。
函数模式 ------ props: (route) => ({ ... })
最灵活 的模式,可以自由组合 params、query 甚至自定义逻辑:
js
{
path: '/search',
component: SearchResult,
props: (route) => ({
keyword: route.query.q,
page: Number(route.query.page) || 1,
from: 'search' // 可注入静态数据
})
}
vue
<!-- SearchResult.vue -->
<script>
export default {
props: {
keyword: { type: String, default: '' },
page: { type: Number, default: 1 },
from: { type: String }
}
}
</script>
⚠️ 注意: 函数模式中
route是当前路由对象,不要在函数内访问this------路由配置在组件实例化之前执行。
Vue Router 4(Vue3)中的 props 配置
好消息:Vue Router 4 的 props API 与 Vue Router 3 完全兼容,上手零成本。
三种模式示例(Vue3 Composition API)
js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
// 布尔模式
{
path: '/user/:id',
component: () => import('@/views/User.vue'),
props: true
},
// 对象模式
{
path: '/about',
component: () => import('@/views/About.vue'),
props: { title: '关于我们' }
},
// 函数模式
{
path: '/products',
component: () => import('@/views/Products.vue'),
props: (route) => ({
category: route.query.category || 'all',
sort: route.query.sort || 'default'
})
}
]
export default createRouter({
history: createWebHistory(),
routes
})
vue
<!-- User.vue(Vue3) -->
<script setup>
defineProps({
id: String
})
</script>
<template>
<div>用户ID:{{ id }}</div>
</template>
命名视图的 props
路由有多个命名视图时,可以为每个视图单独设置 props:
js
{
path: '/dashboard/:id',
components: {
default: Dashboard,
sidebar: Sidebar,
header: HeaderNav
},
props: {
default: true, // 将 params.id 传给 Dashboard
sidebar: (route) => ({ // 函数模式
menu: route.query.menu || 'main'
}),
header: { title: '控制台' } // 静态数据
}
}
嵌套路由的 props 传递
js
{
path: '/user/:userId',
component: UserLayout,
props: true, // userId 传给 UserLayout
children: [
{
path: 'profile',
component: UserProfile,
props: true // userId 也传给 UserProfile
},
{
path: 'posts',
component: UserPosts,
props: (route) => ({
userId: route.params.userId, // 父级参数需要手动传递
page: Number(route.query.page) || 1
})
}
]
}
⚠️ 嵌套路由常见坑: 父级
props: true只把params传给父组件,不会自动透传 给子路由组件。子路由组件要么也声明props: true,要么用函数模式自行组装。
Vue2 vs Vue3 对比一览
| 特性 | Vue Router 3(Vue2) | Vue Router 4(Vue3) |
|---|---|---|
| 布尔模式 | ✅ 映射 params | ✅ 映射 params |
| 对象模式 | ✅ 静态数据 | ✅ 静态数据 |
| 函数模式 | ✅ 灵活组合 | ✅ 灵活组合 |
| 命名视图 props | ✅ 支持 | ✅ 支持 |
| 嵌套路由自动透传 | ❌ 不会 | ❌ 不会 |
| TypeScript 推导 | ⚠️ 较弱 | ✅ 强类型推导 |
| Composition API | ❌ Options API | ✅ defineProps |
实际场景对比
🚫 不用的糟糕写法
vue
<template>
<div>
<h1>{{ $route.params.id }} 的文章</h1>
<p>分类:{{ $route.query.category }}</p>
</div>
</template>
<script>
export default {
mounted() {
this.fetchData(this.$route.params.id)
},
methods: {
fetchData(id) { /* 模拟请求 */ }
}
}
</script>
三个问题: 耦合 $route、无法复用、难以测试。
✅ 使用 props 的最佳实践
js
// 路由配置
{
path: '/user/:id/articles',
component: UserArticles,
props: (route) => ({
userId: route.params.id,
category: route.query.category || 'all'
})
}
vue
<!-- UserArticles.vue(Vue3) -->
<script setup>
defineProps({
userId: { type: String, required: true },
category: { type: String, default: 'all' }
})
// 直接使用 props,与路由完全解耦
</script>
三大优势:
- ✅ 组件纯组件化------传入什么渲染什么
- ✅ 单元测试只需
mount(Component, { props }),无需模拟路由 - ✅ 可在任何地方复用该组件
函数模式进阶用法
💡 参数类型转换
js
props: (route) => ({
id: Number(route.params.id), // 字符串 → 数字
page: parseInt(route.query.page) || 1,
tags: route.query.tags ? route.query.tags.split(',') : []
})
💡 路由级权限控制
js
props: (route) => ({
permission: route.meta.requiredPermission || 'guest',
role: route.meta.role
})
💡 合并多个来源
js
props: (route) => ({
...route.params,
...route.query,
from: 'router',
timestamp: Date.now()
})
💡 默认值与兜底
js
props: (route) => {
const { id, tab = 'overview' } = route.params
return {
id: id || 'default',
tab,
query: route.query
}
}
总结:一句话记住
三种模式,一个原则:让组件不知道路由的存在。
| 模式 | 适用场景 |
|---|---|
props: true |
简单场景,只需 params |
props: { ... } |
传递不变静态配置 |
props: (route) => ({ ... }) |
复杂场景,需要组合参数 |
最佳实践建议: 在新项目中对所有带路由参数的组件统一使用 props 配置,养成"路由只管路由,组件只管 props"的习惯。这对长期维护和团队协作有百利而无一害。
📝 实战小贴士
如果你正在从 Vue2 迁移到 Vue3,props 配置的迁移成本几乎为零------API 完全兼容。唯一需要留意的是:
- Vue3
script setup中用defineProps接收即可 - TypeScript 项目下类型推导比 Vue2 好得多,强烈建议配合 TS 使用
- 大型项目 建议团队统一约定:所有带路由参数的页面组件都必须配合
props配置
如果你已经用了 props: true,不妨逐步升级为函数模式------多花 3 行代码,换来组件完全解耦,值。
如果觉得有帮助,点赞 👍 收藏 ⭐ 关注 🔔 三连支持一下! 更多 Vue 实战干货持续输出中 💪