一、为啥推荐用 props 传路由参数?
不少刚接触Vue Router的同学,在页面跳转传参时,对“push 结合 props 传参”有点懵:明明知道 query、params 能传参,为啥要用 props?push 时咋配合 props 把参数传给组件?今天就把这些问题拆碎了讲明白~
先想个场景:列表页点某条数据,跳转到详情页展示数据 ID,常规做法是用 `$route.params.id` 拿参数,但这样组件和路由强绑定了——测试时得模拟 `$route`,复用组件也得带着路由逻辑,很不灵活。用 props 传参就不一样了:路由把参数“解耦”成组件的 props,组件里只需要声明 props: ['id']
,完全不用管路由怎么传的,这样做有三个核心优势:
- 组件复用性高:比如把详情组件给其他路由用,或者在非路由场景(比如弹窗)里用,直接传 props 就行,不用改内部逻辑。
- 测试更简单:单元测试时,直接给组件传
:id="123"
就能测,不用模拟$route
这种“路由专属”的东西。 - 代码逻辑更清晰:路由负责“路由层”的参数解析,组件只负责“视图层”的渲染,分工明确,后期维护一眼能看懂。
对比 query/params 直接从 $route
拿值的写法,props 传参相当于给组件加了一层“参数过滤”,把路由细节和组件逻辑隔开,这也是 Vue 组件化思想的体现~
路由配置里咋开启 props 传参?
Vue Router 的路由规则里,有个 props
选项,它决定了“怎么把路由参数转成组件的 props”,这个选项有 布尔值、对象、函数 三种玩法,先一个个拆:
布尔值模式:把动态路由的 params 直接当 props
适合“动态路由 + 参数透传”的场景,比如路由配置长这样:
{ path: '/user/:userId', component: UserDetail, props: true }
这里 props: true
的意思是:把路由中 :userId
对应的 params(也就是 URL 里的动态段),直接传给组件的 props。
组件里只需要声明:
export default { props: ['userId'], // 对应路由里的 :userId mounted() { console.log(this.userId) // 就能拿到路由传的参数 } }
这种模式的关键是 路由必须是动态路由(带 :参数
),params 会自动映射成 props,如果路由是静态的(/user
),开了 props: true
也没用,因为没 params 可以传~
对象模式:给组件传静态 props
如果想给组件传“固定值”,不管路由怎么变都不变,用对象模式。
{ path: '/about', component: About, props: { company: '字节', slogan: '激发创造' } }
组件里声明 props: ['company', 'slogan']
,不管怎么跳转到 /about
,这两个值都是固定的。
这种模式适合“路由对应组件需要固定配置”的场景,比如官网关于页的品牌信息,不太会变,用对象模式传静态值很方便~
函数模式:灵活处理路由参数(params/query 都能玩)
如果想结合 params、query,甚至加自定义逻辑处理参数,就得用函数模式,路由配置里 props
是个函数,接收 route
对象(包含 params、query、path 等信息),返回一个对象作为 props。
举个实际例子:商品列表页,需要从 query 里拿 sort
(排序方式)和 page
(页码),但 page
要转成数字,且默认值为 1,路由配置可以这样写:
{ path: '/goods', component: GoodsList, props: (route) => ({ sort: route.query.sort, page: parseInt(route.query.page) || 1 }) }
组件里声明 props: ['sort', 'page']
,当用 router.push({ path: '/goods', query: { sort: 'price', page: '3' } })
跳转时,组件拿到的 page
是数字 3;如果没传 page
,page
1。
函数模式的灵活性在于:能整合 params、query,还能加逻辑处理(比如类型转换、默认值),特别适合复杂的列表筛选、多条件跳转场景~
push 跳转时咋配合 props 传参?
router.push 的参数可以是字符串路径,也可以是对象(包含 name、path、params、query 等),但要注意:push 本身并不直接传 props,而是通过路由的 props 配置,把 push 带的 params/query 转成 props,所以得结合上面的路由配置模式,分情况看怎么传:
布尔值模式下的 push 传参(动态路由场景)
路由配置是 props: true
+ 动态路由(:userId
),这时候 push 要传 params
来匹配动态段。
比如列表页跳转到用户详情:
// 列表页的点击事件里 router.push({ name: 'userDetail', // 用命名路由更稳,避免 path 拼接出错 params: { userId: 123 } })
路由配置:
{ name: 'userDetail', path: '/user/:userId', component: UserDetail, props: true }
组件里通过 props: ['userId']
接收,就能拿到 123,这里的关键是 push 的 params 要和路由的动态段 :userId
对应上,否则 params 会丢(URL 里没显示,组件也拿不到)。
函数模式下的 push 传参(结合 query/params)
函数模式的路由配置,依赖 route
对象里的 params/query,push 时要传对应的 query 或 params。
比如之前的商品列表例子,想传排序和页码:
// 筛选栏点击“按价格排序”并跳转到第 2 页 router.push({ path: '/goods', query: { sort: 'price', page: '2' } })
路由的 props 函数会拿到 route.query.sort
和 route.query.page
,处理后传给组件,组件里直接用 sort
和 page
这两个 props 就行,完全不用管路由层的 query 怎么处理~
对象模式下的 push 传参(静态值场景)
对象模式的 props 是“写死”在路由里的,push 传参不影响 props(因为 props 是静态对象),比如路由配置 props: { company: '字节' }
,不管 push 传啥,组件拿到的 company 都是“字节”。
这种场景下,push 主要负责“跳转到这个路由”,参数由路由自己控制,适合不需要动态传参的页面~
组件接收 props 要注意啥?
路由把参数转成 props 后,组件得“接住”这些值,否则白传了,这一步有两个关键点:
必须声明 props 选项
组件里要明确写 props: ['参数名']
(或对象形式),否则 Vue 不会把路由传的参数绑定到组件实例上,比如动态路由传 userId
,组件里得写:
export default { props: { userId: { type: Number, // 类型校验,避免传错类型 required: true // 必传,因为路由里有动态段 } }, mounted() { console.log(this.userId) // 正确拿到值 } }
如果没声明 props,直接用 this.userId
会报错,因为组件实例上根本没有这个属性~
处理可选参数(避免 undefined)
如果路由参数是“可选”的(query 里的参数可能不存在),要在 props 里加默认值,比如函数模式里的 page
参数,我们用了 parseInt(route.query.page) || 1
做默认值;如果是布尔值模式,动态路由的 params 是必传的(否则 URL 不匹配),所以可以用 required: true
。
举个错误案例:路由是 /user/:userId?
(userId 可选),props 开了 true
,但组件里 props: { userId: Number }
没加默认值,这时候如果 push 没传 userId,组件里 this.userId
undefined
,可能导致渲染错误。
解决办法:要么让路由参数必传(去掉 ),要么在 props 里加默认值:
props: { userId: { type: Number, default: 0 // 没传时默认用 0 } }
实战:从列表到详情页的完整流程
光讲理论太虚,结合实际需求走一遍流程,你就懂了~
需求:文章列表页,点击某篇文章,跳转到详情页,展示文章 ID 和标题;详情页组件要复用(比如以后做弹窗预览),不能依赖 $route
。
步骤 1:配置路由(用布尔值模式)
// router.js const routes = [ { path: '/article/:articleId', name: 'ArticleDetail', component: () => import('./views/ArticleDetail.vue'), props: true // 开启 props 传参,把 :articleId 转成 props } ]
步骤 2:列表页 push 跳转
列表页里,每个文章项的点击事件:
<!-- ArticleList.vue --> <template> <div> <div v-for="article in articleList" :key="article.id" @click="goToDetail(article)" > {{ article.title }} </div> </div> </template> <script> export default { data() { return { articleList: [ { id: 1, title: 'Vue Router 传参指南' }, { id: 2, title: 'Props 解耦的艺术' } ] } }, methods: { goToDetail(article) { this.$router.push({ name: 'ArticleDetail', // 用命名路由,避免 path 硬编码 params: { articleId: article.id } // 传动态参数 }) } } } </script>
步骤 3:详情页接收 props 并渲染
<!-- ArticleDetail.vue --> <template> <div> <h1>文章 ID:{{ articleId }}</h1> <p>标题:{{ articleTitle }}</p> </div> </template> <script> export default { props: { articleId: { type: Number, required: true }, articleTitle: { type: String, required: true } }, // 假设从接口拿标题,这里用计算属性模拟 computed: { articleTitle() { // 实际项目里可能调接口:axios.get(`/api/article/${this.articleId}`) return `模拟标题_${this.articleId}` } } } </script>
这样一来,详情页完全不依赖 $route
,以后要做弹窗预览,直接写 <ArticleDetail :article-id="3" />
就能用,路由和组件彻底解耦~
常见坑点 & 解决办法
用 props 传参时,这些“小陷阱”很容易踩,提前避坑:
坑 1:路由 props 开了 true,但 push 没传 params
比如路由是 /user/:userId
,props: true,但 push 时写成 this.$router.push('/user')
(没传 userId),这时候 URL 会变成 /user
(动态段丢了),组件里的 userId
也是 undefined
。
解决:push 时必须传 params 匹配动态路由,或者把路由改成可选动态段(/user/:userId?
),同时组件 props 加默认值。
坑 2:组件没声明 props,直接用 this.xxx
比如路由传了 userId
,但组件里没写 props: ['userId']
,直接用 this.userId
,Vue 会报错“属性或方法未定义”。
解决:组件里必须显式声明 props,把要接收的参数列出来。
坑 3:函数模式下,query 参数类型不对
比如路由 props 函数里把 query.page 转成数字,但 push 时传的是字符串 page: 'abc'
,parseInt 后变成 NaN,导致组件逻辑出错。
解决:在 props 函数里加类型校验逻辑,
props: (route) => ({ page: route.query.page ? parseInt(route.query.page) : 1 })
或者在 push 前先处理参数类型,确保传给路由的是合法值。
props 传参让路由更优雅
Vue Router 里用 push 结合 props 传参,核心是通过路由的 props 选项,把路由参数(params/query)转成组件的 props,让组件和路由解耦。
- 简单场景(动态路由 + 透传参数)用布尔值模式,传 params 就行;
- 固定值场景用对象模式,路由里写死 props;
- 复杂场景(多参数、类型转换、默认值)用函数模式,灵活处理 route 对象里的参数;
这样做不仅让组件更纯净、复用性更强,还能让整个项目的路由逻辑和组件逻辑分工更清晰,下次写项目时,别再只知道 query/params 直接拿值啦,试试 props 传参,代码会清爽很多~
(如果还有疑问,命名路由和路径路由在 push 时传参有啥区别?”“props 函数里能结合 store 吗?”这些进阶问题,评论区喊我,咱们再展开唠~)
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。