一、先分清参数的两种类型,动态路由 params 查询参数 query
p>做前端开发时,你肯定遇到过这类场景:商品列表页选了筛选条件,点进详情页再返回,筛选条件没了;多步骤表单填了一半,跳下一步后上一步内容丢了……这时候就需要“保留参数(keep params)”,那Vue Router 怎么实现参数保留?不同场景得用不同方法,咱一步步拆解。
要解决“保留参数”问题,得先明白Vue Router里参数分两类,处理逻辑天差地别:
动态路由参数(params)
是URL路径里的动态片段,比如配置路由 { path: '/user/:id', name: 'User' }
,这里的 :id
params,它有两个关键特点:
- 不在URL查询字符串里(history模式下),刷新页面时若服务端没做 fallback 配置,参数可能丢失(hash模式相对稳定但URL丑);
- 用
path
跳转时(router.push('/user/123')
),params 会被直接忽略,必须用name + params
方式(router.push({ name: 'User', params: { id: 123 } })
)才能传参。
查询参数(query)
是URL里 后面的键值对,/list?keyword=vue&page=2
,对应 $route.query
,它的优势很明显:
- 刷新页面不会丢(因为参数写在URL里);
- 用
path
或name
跳转都能传,router.push({ path: '/list', query: { keyword: 'vue' } })
或router.push({ name: 'List', query: { keyword: 'vue' } })
都生效。
场景1:跳转后返回,列表页“筛选条件”不丢
做商品列表、搜索页时,用户选了关键词、分页、筛选标签,点进详情页再返回,希望这些条件还在,这种场景用 “query参数 + 组件内响应式处理” 最稳妥。
(1)把筛选条件存在query里,让URL“状态
把筛选条件塞到 query
里,URL会跟着变化,返回时自然能从 $route.query
里拿到参数,举个实际开发的例子:
<template> <div class="list-page"> <!-- 搜索输入框 --> <input v-model="keyword" placeholder="搜点啥..." /> <button @click="handleSearch">搜索</button> <!-- 跳详情页 --> <router-link :to="{ name: 'Detail', params: { id: 1 } }">去商品详情</router-link> </div> </template> <script setup> import { useRouter } from 'vue-router' import { ref, watch } from 'vue' const router = useRouter() const keyword = ref('') // 点击搜索时,更新URL的query const handleSearch = () => { router.push({ name: 'List', query: { keyword: keyword.value } }) } // 组件加载/query变化时,同步更新输入框和列表数据 watch( () => router.currentRoute.value.query, (newQuery) => { keyword.value = newQuery.keyword || '' // 调接口重新获取列表数据 fetchProductList(newQuery.keyword) }, { immediate: true } // 组件一加载就执行一次 ) </script>
这样,用户从详情页(如 /detail/1
)返回列表页时,$route.query.keyword
还在,输入框内容和列表数据也能自动恢复。
(2)用keep-alive缓存组件状态(适合“临时未提交”的状态)
如果参数是组件内临时状态(比如输入框还没点“搜索”,不想让URL变化),可以用 keep-alive
缓存组件实例,让状态不丢失,步骤分两步:
① 路由配置里给需要缓存的路由加 meta.keepAlive
标记:
const routes = [ { path: '/list', name: 'List', component: List, meta: { keepAlive: true } // 标记该路由组件要缓存 }, { path: '/detail/:id', name: 'Detail', component: Detail } ]
② 在App.vue里,根据 meta.keepAlive
决定是否用 keep-alive
包裹:
<template> <div id="app"> <!-- 缓存需要keepAlive的组件 --> <keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <!-- 不缓存的组件直接渲染 --> <router-view v-else></router-view> </div> </template>
这样,List组件被缓存后,用户输入的临时内容(比如还没点搜索的输入框文字)在返回时不会被清空,但要注意:这种方式存的是组件实例的状态,不是URL里的参数,刷新页面就没了,适合临时状态,不适合需要持久化的筛选条件~
场景2:动态路由 params 的传递与保留
动态路由 params 是路径里的 /:id
这类参数(比如用户页 /user/123
里的 123
),它的“坑”在于:当路由name相同、只是params变化时,组件会复用,导致生命周期钩子不触发,数据不更新。
(1)跳转时必须用“name + params”传参
动态路由传参,必须用 name + params
的方式,否则params会丢失,比如从 /user/123
跳到 /user/456
:
// 正确:用name + params,确保params能传过去 router.push({ name: 'User', params: { id: 456 } }) // 错误:用path的话,params会被忽略! router.push('/user/456')
(2)组件复用后,监听 params 变化更新数据
因为路由name相同(都是User),只是 params.id
变了,Vue Router会复用组件实例,created
、mounted
这些钩子不会再执行,这时候要用watch监听 $route.params
的变化,主动更新数据。
举个用户详情页的例子,根据不同id获取用户信息:
<template> <div class="user-page">用户ID:{{ userId }}</div> </template> <script setup> import { useRoute, watch } from 'vue-router' import { ref } from 'vue' const route = useRoute() const userId = ref('') // 监听params.id变化,更新用户数据 watch( () => route.params.id, (newId) => { userId.value = newId fetchUserInfo(newId) // 调接口拿新用户数据 }, { immediate: true } // 组件加载时先执行一次 ) </script>
场景3:跨页面/刷新后,参数还能保留(持久化)
如果参数需要在多个页面共享,甚至刷新后也不丢,就得用 “状态管理工具(Vuex/Pinia)+ 本地存储(可选)” 组合拳了。
(1)用Pinia/Vuex存全局参数
以Pinia为例,创建一个专门存参数的store,让参数在全局流通,比如做一个全局筛选条件的store:
// stores/paramsStore.js import { defineStore } from 'pinia' export const useParamsStore = defineStore('params', { state: () => ({ globalKeyword: '', // 全局共享的搜索关键词 currentTab: 'home' // 全局共享的标签页 }), actions: { setGlobalKeyword(keyword) { this.globalKeyword = keyword }, setCurrentTab(tab) { this.currentTab = tab } } })
然后在组件里用这个store,比如列表页设置全局关键词:
<template> <div class="list-page"> <input v-model="localKeyword" @input="updateGlobalKeyword" /> </div> </template> <script setup> import { useParamsStore } from '@/stores/paramsStore' import { computed } from 'vue' const paramsStore = useParamsStore() // 用computed双向绑定store里的globalKeyword const localKeyword = computed({ get() { return paramsStore.globalKeyword }, set(val) { paramsStore.setGlobalKeyword(val) } }) // 或者直接绑定事件 const updateGlobalKeyword = (e) => { paramsStore.setGlobalKeyword(e.target.value) } </script>
另一个页面(比如详情页)要拿这个参数,直接读store就行:
<template> <div class="detail-page"> 全局关键词:{{ paramsStore.globalKeyword }} </div> </template> <script setup> import { useParamsStore } from '@/stores/paramsStore' const paramsStore = useParamsStore() </script>
(2)结合localStorage/sessionStorage实现“真正持久化”
如果想刷新页面后参数还在,就把store里的状态同步到本地存储,Pinia可以用 persist
插件,也能自己写逻辑:
// stores/paramsStore.js import { defineStore } from 'pinia' export const useParamsStore = defineStore('params', { state: () => ({ // 从localStorage恢复数据,没有就用默认值 globalKeyword: localStorage.getItem('globalKeyword') || '', currentTab: sessionStorage.getItem('currentTab') || 'home' }), actions: { setGlobalKeyword(keyword) { this.globalKeyword = keyword localStorage.setItem('globalKeyword', keyword) // 存到localStorage }, setCurrentTab(tab) { this.currentTab = tab sessionStorage.setItem('currentTab', tab) // 存到sessionStorage } } })
这样,即便页面刷新,localStorage/sessionStorage里的参数能恢复到store中,实现“刷新也不丢”的持久化效果。
场景4:父子路由间,参数的继承与传递
如果是父路由跳子路由(/parent/123/child
),想让子路由拿到父路由的params或query,可以用 导航守卫 处理。
(1)父路由离开时,把参数传给子路由
在父组件的 beforeRouteLeave
守卫里,修改目标路由(子路由)的query或params,比如父路由是 /parent/:id
,子路由是 /parent/:id/child
,想把父的id传给子的query:
// Parent.vue import { defineComponent } from 'vue' import { useRoute, onBeforeRouteLeave } from 'vue-router' export default defineComponent({ setup() { const route = useRoute() onBeforeRouteLeave((to, from, next) => { // to是目标路由(子路由),给它的query加parentId to.query = { ...to.query, parentId: route.params.id } next() // 继续跳转 }) } })
子路由组件里,就能通过 $route.query.parentId
拿到父路由的 params.id
了。
(2)子路由进入时,从父路由拿参数
在子组件的 beforeRouteEnter
守卫里,从 from
路由(父路由)里取参数,再传给子组件实例:
// Child.vue import { defineComponent } from 'vue' import { onBeforeRouteEnter } from 'vue-router' export default defineComponent({ setup() { onBeforeRouteEnter((to, from, next) => { // from是父路由,取它的params.id const parentId = from.params.id // 通过next的回调,把parentId传给子组件实例 next((vm) => { vm.parentId = parentId }) }) } })
不同场景选对应方法,不纠结
- 想让URL可见、刷新不丢 → 用 query参数,配合
router.push({ query: {} })
和watch $route.query
; - 只是组件内临时状态,返回时不想丢 → 用 keep-alive + meta.keepAlive;
- 动态路由参数变化,组件复用 → 用 watch $route.params;
- 跨页面/刷新后还得有 → 用 Pinia/Vuex + 本地存储;
- 父子路由传参 → 用 导航守卫(beforeRouteLeave / beforeRouteEnter)。
核心思路就一句话:先想清楚参数的“作用范围”和“持久化需求” —— 是只在当前页面生效?跨页面共享?需不需要刷新保留?想明白这些,再选对应的技术手段,就不会绕弯路啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。