Vue3 router params传参注意事项
最近在帮团队里刚接触Vue3的小伙伴搭项目,连续两天都碰到同一个问题:“为啥我跳转页面后,刷新一下params就没了?”“params和query混着用会不会有冲突?”其实这些都是Vue3 Router Params在实际开发中高频出现的点,刚好今天有空整理成大家能直接用的问答+避坑手册,全是踩坑踩出来的干货,连官方文档里没特别强调但踩雷概率极高的细节都加进去了。
Vue3 Router里,params和query到底有啥本质区别?
这个问题是避坑的基础,很多人只停留在“params在URL里不显示/query显示”的表面,其实本质是路由匹配规则、数据持久化逻辑、触发路由更新的条件完全不一样。
首先说URL呈现:query参数是带在路由查询字符串里的,比如/user?name=张三&id=123,不管用什么方式刷新、分享给别人、收藏页面,这些参数都会完整保留;params参数默认是不带在URL显示区的(除非你配置了动态路由占位符),占位符配置的params比如/user/:id里的id,刷新后会留,但没有占位符的“隐藏params”,刷新、分享、收藏全都会丢失——开头小伙伴遇到的就是这个没配置占位符的问题。
然后是路由匹配的硬性要求:query参数完全不影响路由匹配,比如你定义了/user路由,不管有没有带?name=xxx,都能匹配到;但params不一样,如果占位符里的参数是必填的(没有加可选标记),不传就会匹配404,比如/user/:id必须传id,/user就跳不过去;可选的比如/user/:id?,不传id也能匹配到,只是route.params.id的值是undefined。
还有个隐藏点是路由守卫的获取顺序:虽然一般情况下不影响开发,但如果你的守卫里同时依赖了query和params,记住params的优先级比query高一点——比如你在同一个跳转里同时传了占位符params的id和query的id,守卫里先拿到的是params的id,但模板或者组件里要自己注意区分,别搞错变量名。
隐藏params真的完全不能用吗?用的时候要注意什么?
很多新手怕用隐藏params丢数据,但其实它在一些短流程的场景里特别好用,比如从列表页跳转到弹窗详情页(不需要刷新也不需要分享链接),或者两步验证里的中间跳转,但要用对,不能乱用,必须满足这几个条件才不会踩雷:
第一,短流程的临时数据传递场景,比如刚才说的弹窗详情,点进详情不需要单独的URL入口,分享给别人别人直接点链接看不到详情,刚好适合用隐藏params;如果是需要单独访问、分享的页面,比如文章详情页、用户个人中心,绝对不能用隐藏params,必须配置动态路由占位符。
第二,要给数据留个“兜底存储”,哪怕是短流程,万一用户手滑刷新了怎么办?可以搭配localStorage、sessionStorage或者Vuex/Pinia来存一份,跳转的时候先存,组件挂载的时候先检查路由里有没有params,没有的话再从存储里取,取完之后记得及时删除存储里的数据,避免污染下次操作——比如两步验证里,第一步跳转传验证码到第二步的隐藏params,同时存到sessionStorage,第二步挂载时先取params,没取到就看sessionStorage,用户提交成功或返回第一步后,立刻清空sessionStorage的验证码。
第三,Vue3 Router 4.x里,隐藏params必须用name跳转,不能用path,这点是Vue3和Vue2最大的区别之一!很多人从Vue2转过来直接用path传隐藏params,结果就是params完全传不过去——官方文档里明确说了,用path跳转时,所有的params都会被忽略,不管有没有占位符;只有用name跳转时,占位符和隐藏的params才会生效,比如正确的隐藏params跳转应该是:
import { useRouter } from 'vue-router'
const router = useRouter()
// 必须用name,不能用path
router.push({
name: 'UserDetail',
params: {
id: 123,
name: '张三',
age: 25
}
})
如果是Vue2,用path传隐藏params可能还能用,但Vue3 Router 4.x已经彻底废弃了这个用法,这点一定要注意!
配置了动态路由占位符的params,刷新会丢吗?怎么正确使用?
配置了动态路由占位符的params(不管是必填还是可选),刷新是不会丢的,因为它已经嵌在URL里了——比如/user/123/张三,对应路由配置是{ path: '/user/:id/:name?', name: 'UserDetail', component: UserDetail },这里id是必填的,name是可选的,刷新后route.params.id还是123,route.params.name还是张三或者undefined。
但配置动态路由占位符也有几个坑要注意:
第一个坑是参数类型的问题,动态路由占位符里的参数不管你传的是数字、布尔值还是对象,最终都会被转换成字符串!比如你传了id=123(数字),刷新后route.params.id就变成了"123",很多人这里会出问题,比如用判断数字,结果就变成false了——解决办法很简单,要么在组件里接收的时候转类型,比如const userId = Number(route.params.id),要么在路由配置里用props的函数形式转类型:
// 路由配置
{
path: '/user/:id/:name?',
name: 'UserDetail',
component: UserDetail,
// 用props函数形式转类型
props: (route) => ({
id: Number(route.params.id),
name: route.params.name || '匿名用户'
})
}
这样在组件里直接用defineProps接收就可以了,不用再转类型:
// UserDetail.vue const props = defineProps(['id', 'name']) console.log(props.id) // 123(数字) console.log(props.name) // 张三或匿名用户
第二个坑是多段占位符的匹配问题,比如你定义了两个路由:{ path: '/user/:id', name: 'UserDetail', component: UserDetail }和{ path: '/user/settings', name: 'UserSettings', component: UserSettings },那访问/user/settings的时候,到底匹配哪个?这里Vue Router的匹配规则是“先定义先匹配”,如果你把UserDetail放在UserSettings前面,访问/user/settings就会匹配到UserDetail,把settings当成id的字符串——解决办法就是把固定路径的路由放在动态路径的前面,
// 路由配置顺序很重要!
const routes = [
{
path: '/user/settings',
name: 'UserSettings',
component: UserSettings
},
{
path: '/user/:id',
name: 'UserDetail',
component: UserDetail
}
]
这样访问/user/settings就会先匹配到固定路径的UserSettings,而不会当成id处理了。
第三个坑是特殊字符的问题,动态路由占位符里的参数如果包含、、等特殊字符,直接传的话会被当成URL的一部分处理,导致匹配失败——解决办法是用encodeURIComponent编码,接收的时候用decodeURIComponent解码,比如跳转的时候:
router.push({
name: 'UserDetail',
params: {
id: 123,
// 包含特殊字符的参数要编码
name: encodeURIComponent('张三/李四?王五#赵六')
}
})
接收的时候(不管是直接取route.params还是用props函数形式)都要解码:
// 直接取route.params的情况
import { useRoute } from 'vue-router'
const route = useRoute()
const userName = decodeURIComponent(route.params.name || '')
// 用props函数形式的情况
props: (route) => ({
id: Number(route.params.id),
name: route.params.name? decodeURIComponent(route.params.name) : '匿名用户'
})
params和query可以一起用吗?一起用的时候要注意什么?
当然可以一起用,而且很多场景都需要一起用,比如电商平台的商品列表页,需要用path+动态params传分类ID(固定显示在URL里,方便分享收藏),用query传筛选条件(比如价格区间、排序方式、页码,这些经常变,放在query里用户可以直接调整URL参数来筛选),比如/goods/electronics?minPrice=1000&maxPrice=5000&sort=price-asc&page=2。
一起用的时候要注意这几点:
第一,不要传重复的变量名,比如刚才说的电商场景,别同时在params和query里传page,不然你会混淆到底取哪个——最好给参数分类,比如和路由路径强相关的、固定不变或少变的放params(比如分类ID、商品ID),和筛选、分页、搜索相关的、经常变的放query。
第二,一起用的时候路由匹配还是只看path和params占位符,query完全不影响,比如刚才的/goods/electronics路由,不管有没有带query参数,都能匹配到。
第三,params用name跳转,query可以用name也可以用path,因为刚才说了用path跳转时params会被忽略,但query不会,所以如果你只传query不传params,可以随便用name或path,但如果同时传params和query,必须用name跳转,不然params传不过去。
Vue3 Router 4.x里,params更新了但组件没重新渲染怎么办?
这个问题也是高频出现的,比如你从/user/123跳转到/user/456,都是同一个UserDetail组件,这时候Vue会复用组件,不会重新创建,所以组件的onMounted不会触发,你在onMounted里写的获取用户数据的代码也不会执行,导致页面显示的还是原来的123用户数据。
解决办法有三个,根据你的场景选:
第一个办法是用onBeforeRouteUpdate钩子,这个钩子是Vue Router专门为组件复用设计的,当路由参数更新但组件复用时会触发,你可以在这里重新获取数据:
import { onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
const user = ref(null)
const getUserData = async (id) => {
// 这里写获取用户数据的接口
user.value = await fetch(`/api/user/${id}`).then(res => res.json())
}
// 组件第一次挂载时获取数据
onMounted(() => getUserData(Number(route.params.id)))
// 路由参数更新时重新获取数据
onBeforeRouteUpdate(async (to, from) => {
await getUserData(Number(to.params.id))
})
第二个办法是给router-view加key,key可以是路由的fullPath,也可以是params的唯一标识,比如刚才的/user/123和/user/456,fullPath不一样,key不一样,Vue就不会复用组件,会重新创建,onMounted就会触发:
<!-- App.vue --> <router-view :key="$route.fullPath" /> <!-- 或者如果只有id变化,用params.id当key更高效 --> <router-view :key="$route.params.id" />
这个办法最简单,但性能会比第一个办法差一点,因为要重新创建整个组件,而不是只更新数据——如果组件比较小,或者你不想写onBeforeRouteUpdate钩子,可以用这个办法;如果组件比较大,建议用第一个办法。
第三个办法是用watch监听route.params的变化,这个办法和onBeforeRouteUpdate类似,只是触发的时机稍微晚一点,onBeforeRouteUpdate是在导航确认前触发,watch是在导航确认后、DOM更新前触发,一般情况下区别不大:
import { useRoute } from 'vue-router'
import { ref, watch } from 'vue'
const route = useRoute()
const user = ref(null)
const getUserData = async (id) => {
user.value = await fetch(`/api/user/${id}`).then(res => res.json())
}
// 组件第一次挂载时获取数据
onMounted(() => getUserData(Number(route.params.id)))
// 监听params的变化,注意要用immediate: true吗?
// 不用,因为第一次onMounted已经获取了,除非你想让第一次也通过watch获取,那可以加
watch(
() => route.params.id,
(newId) => {
getUserData(Number(newId))
}
)
还有哪些不常见但容易踩雷的params细节?
刚才说的都是常见的,还有几个不常见但踩雷后很难找原因的细节:
第一个是可选多段占位符的问题,比如你定义了{ path: '/user/:id+', name: 'UserDetail', component: UserDetail },这里的表示可以有多段id,比如/user/123/456/789,这时候route.params.id会变成一个数组['123', '456', '789'],刷新后也不会丢,但转类型的时候要注意,不能直接转整个数组,要遍历转。
第二个是通配符占位符的问题,比如你定义了{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },这里的通配符可以匹配所有路径,比如/abc/def/ghi,这时候route.params.pathMatch会变成一个数组['abc', 'def', 'ghi'],这个主要用来做404页面,偶尔也用来做自定义路径的解析。
第三个是Vue3 Router 4.x里,params的默认值只能通过props函数形式设置,不能直接在params里传默认值,比如你在路由配置里传了params的默认值,比如{ name: 'UserDetail', params: { id: 123 } },这个是没用的,只有通过props函数形式设置的默认值才会生效,刚才已经举过例子了。
第四个是导航守卫里修改params的问题,比如你在beforeEach里修改了params,比如to.params.id = 456,这个在Vue3 Router 4.x里是不允许的,因为to和from都是只读的,你要修改的话,只能通过next或者return来重新导航,
// beforeEnter守卫的例子
beforeEnter: (to, from, next) => {
if (to.params.id === '123') {
// 重新导航到id=456的页面
next({
name: 'UserDetail',
params: { id: 456 },
query: to.query // 保留原来的query参数
})
} else {
next()
}
}
// 或者用Composition API的beforeEach,用return
router.beforeEach((to, from) => {
if (to.params.id === '123') {
return {
name: 'UserDetail',
params: { id: 456 },
query: to.query
}
}
})
好了,今天关于Vue3 Router Params的注意事项就整理到这里了,全是我和团队小伙伴踩过的坑,希望能帮到大家,如果还有其他问题,欢迎在评论区留言讨论~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网

