在 Vue Router 中,query 和 params 是两种最常用的传参方式。它们最直观的区别就像是 GET 请求与 POST 请求在地址栏表现上的差异。
1. 核心区别对照表
| 特性 | Query 传参 | Params 传参 |
|---|---|---|
| 表现形式 | URL 后面带参数:/path?id=123 | URL 路径的一部分:/path/123 |
| 定义方式 | 不需要配置路由路径,直接传 | 需要在路由配置中定义占位符(如 :id) |
| 刷新丢失 | 不会丢失(参数在 URL 中) | 会丢失(如果不配合动态路由匹配) |
| 属性获取 | $route.query | $route.params |
| 跳转方式 | name 或 path 均可 | 必须通过 name 跳转(Vue Router 4.x 限制) |
2. 详细拆解
Query 传参(类似搜索)
query 传递的参数会显示在地址栏,适合“非敏感、可分享”的数据,比如搜索关键字、分页页码等。
- 跳转写法:
javascript
// 跳转后地址:/home?id=1&name=jack
router.push({ path: '/home', query: { id: 1, name: 'jack' } })- 接收参数:
javascript
console.log(this.$route.query.id)Params 传参(类似详情页)
params 通常用于传递特定的标识符。
注意(Vue Router 4.x 重要变更): > 在 Vue 3 中,如果你直接使用
params传递内存中的对象且不在路径中定义它(即隐式传参),刷新页面后参数会消失。官方推荐使用动态路由匹配或状态管理(Pinia/Vuex)替代。
- 路由定义:
javascript
{ path: '/user/:id', name: 'User', component: User }- 跳转写法:
javascript
// 跳转后地址:/user/123
router.push({ name: 'User', params: { id: 123 } })- 接收参数:
javascript
console.log(this.$route.params.id)3. History API 状态传参 (State)
除了 query 和 params,Vue Router 还支持通过 history.state 传递参数。这种方式非常适合传递不希望显示在 URL 中,但又需要保留的复杂对象数据。
特点
- 隐式传递:参数不会显示在 URL 中。
- 刷新保留:利用浏览器 History API 特性,页面刷新后
state依然存在。 - 类型支持:可以传递对象,不需要像 query 那样手动序列化。
使用方法
发送方:
javascript
// router.push 的第二个参数可以包含 state
router.push({
path: '/user/profile',
state: {
from: 'homepage',
userInfo: { name: 'Alice', role: 'admin' }
}
})接收方:
javascript
// 在组件中获取
console.log(history.state.userInfo)注意:虽然刷新页面数据会保留,但如果是新开标签页或分享链接,state 是无法获取到的。它依赖于浏览器的历史记录栈。
4. 什么时候用哪个?
- 使用 Query 的场景:
- 需要做“搜索结果页”的分享。
- 多个可选的筛选条件(如
?color=red&size=m)。 - 普通的页面跳转。
- 使用 Params 的场景:
- 典型的“详情页”(如商品详情
/product/1001)。 - 参数是路由的一部分,对 SEO 更友好。
- 看起来更简洁、美观的 RESTful 风格。
避坑小贴士 💡
- 如果你使用了
path进行跳转,params会被忽略,必须使用name。 - 如果你的
params没在路由 path 里配置:id,那么它就是“临时工”,刷新页面就没了。
解决 Vue Router 4.x 刷新后 params 丢失问题
这是一个非常实用的需求。在 Vue Router 4.x 中,非路径属性的 params 确实会在刷新后丢失。
为了解决这个问题,我们可以利用 localStorage 或 sessionStorage(推荐 sessionStorage,因为它的生命周期通常与当前标签页一致)进行数据持久化。
方案:使用 sessionStorage 守卫参数
这里我用 Vue 3 + Composition API 为例,为你展示如何封装这套逻辑。
1. 发起跳转:存储数据并跳转
在发送方,我们在跳转前先将数据存入本地。
javascript
import { useRouter } from 'vue-router'
const router = useRouter()
const goToDetail = (item) => {
// 1. 先存入本地存储(转换为 JSON 字符串)
sessionStorage.setItem('current_params', JSON.stringify(item))
// 2. 跳转
router.push({
name: 'Detail',
params: { id: item.id } // 即使这里传了,刷新后也会丢,所以我们靠上面那行
})
}2. 接收页面:读取并恢复数据
在接收方页面,我们需要在组件加载时判断 params 是否为空。如果是空的(说明刷新了),就从本地存储里拿。
vue
<template>
<div v-if="detailData">
<h2>详情页 - {{ detailData.name }}</h2>
<p>ID: {{ detailData.id }}</p>
<p>描述: {{ detailData.description }}</p>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const detailData = ref(null)
onMounted(() => {
// 1. 尝试从当前路由参数获取 (第一次跳转过来时有效)
const paramsData = route.params.info
if (paramsData) {
// 如果存在,直接赋值并存一份到本地备用
detailData.value =
typeof paramsData === 'string' ? JSON.parse(paramsData) : paramsData
sessionStorage.setItem('current_params', JSON.stringify(detailData.value))
} else {
// 2. 如果刷新了,从本地存储恢复
const savedData = sessionStorage.getItem('current_params')
if (savedData) {
detailData.value = JSON.parse(savedData)
console.log('数据已从本地存储恢复')
}
}
})
</script>3. 进阶:更好的“干净”做法
如果你觉得每个页面都写这段逻辑太麻烦,可以使用 路由守卫 (Navigation Guards) 统一处理。
在 router/index.js 中拦截:
javascript
router.beforeEach((to, from, next) => {
// 如果是去详情页且携带了 params
if (to.name === 'Detail' && Object.keys(to.params).length > 0) {
localStorage.setItem('temp_params', JSON.stringify(to.params))
}
next()
})💡 核心建议
- 使用
sessionStorage:相比localStorage,它在你关闭浏览器标签页时会自动清除,不会留下冗余的垃圾数据。 - 优先考虑状态管理:如果你的项目较大,建议直接使用 Pinia。把数据存在 Pinia 里,再配合
pinia-plugin-persistedstate插件,可以实现完全自动化的持久化,代码会整洁得多。