Vue3路由传参刷新会丢数据?动态路由、query/params、props这3种方式怎么选不踩坑?
最近刷前端社区和项目群,总能碰到Vue3开发者提路由传参的相关问题:要么是用params传参,一刷新页面数据全空了;要么是把所有数据都塞query里,地址栏长到没法看;要么是props传参只知道布尔值用法,不知道怎么把复杂数据也传得优雅,其实这些问题,本质上是没搞懂Vue Router v4(也就是适配Vue3的版本)几种传参方式的底层逻辑和适用场景,今天就从实际开发场景切入,把这些坑给大家填得明明白白。
先理清楚Vue Router v4的核心传参路径
很多新手刚上手时,可能会把query、params、甚至浏览器的localStorage/sessionStorage混在一起用,其实在Vue3+Vue Router v4的标准体系里,原生支持的核心传参路径只有两条:一条是依赖URL的传参(query、动态路由匹配的params),另一条是通过路由组件的props属性直接传参(底层还是依赖前两条的解析,或者手动配置),至于存储类的方案,是原生传参的补充,用来解决“URL传参不够存/不想暴露/刷新丢了硬参以外的软状态”这类特殊场景的。
先别着急记细节,咱们先锚定几个开发中最常遇到的场景,把场景和路径对应上,后面讲具体用法时就不会乱了。
- 你做了一个商品列表页,点进去要显示对应的商品ID,希望别人复制链接发给朋友也能打开这个商品——肯定选URL路径上的传参;
- 商品列表页筛选后要跳转结果页,筛选条件有几十个(比如颜色、尺码、价格区间、发货地),地址栏不能太乱,但刷新后还要保留筛选状态——这时候可以用路由的query+本地缓存的组合,或者用Vuex/Pinia存,但缓存要注意清理;
- 列表页跳转详情页,除了商品ID,还想带当前页面的页码、滚动位置这些纯前端临时状态,复制链接不需要,刷新也无所谓丢不丢——这时候可以考虑路由组件的props(配合动态路由/query的话也能刷新保留),或者不依赖URL的params(不过Vue Router v4里这种方式坑很多,后面会重点说);
- 详情页有一些敏感数据(比如用户ID、内部订单号),绝对不能暴露在地址栏——这时候只能用Vuex/Pinia、或者sessionStorage,原生URL传参肯定不行。
依赖URL的传参,刷新不丢的“硬通货”
依赖URL的传参,数据是直接写在浏览器地址栏里的,所以只要别人复制了这个链接,或者刷新了页面,数据都会跟着URL一起回来,这也是最推荐的分享友好型传参方式,它又细分为动态路由匹配的params和查询字符串query两种,底层实现和适用场景完全不同,新手最容易搞混这两个,踩的刷新丢数据的坑,大多也是因为搞混了它们的用法。
动态路由匹配的params:写在URL路径里的“必填标识”
什么是动态路由匹配?就是给路由规则里的路径加个“占位符”,比如/product/:id,这里的id就是占位符,当你跳转/product/123时,Vue Router会自动把123解析成params.id,传给对应的组件。
什么时候用动态路由匹配的params?
这种传参方式最适合传“页面必须的唯一标识”,比如商品ID、用户ID、文章ID——因为这些标识是页面存在的基础,没有它们页面根本不知道要展示什么,所以放在URL路径里最自然,也符合RESTful API的设计规范(虽然前端路由不是后端API,但遵循类似的规范会让代码更易读)。
举个例子,你做一个博客系统,路由规则应该这样写:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import BlogList from '@/views/BlogList.vue'
import BlogDetail from '@/views/BlogDetail.vue'
const routes = [
{
path: '/',
name: 'BlogList',
component: BlogList
},
{
path: '/blog/:blogId', // 这里的blogId就是占位符
name: 'BlogDetail',
component: BlogDetail
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default router
跳转的时候有两种方法:一种是用<router-link>组件,一种是用useRouter的push方法,不过这里要注意一个关键点:如果用动态路由匹配的params跳转,最好使用路由的name属性,而不是path属性——因为如果只用path属性,你必须手动把params拼到路径里,比如/blog/${blogId},但如果以后你修改了路由规则里的路径(比如把/blog/:blogId改成了/article/:blogId),你就得找到所有手动拼接path的地方修改,维护成本很高;但如果用name属性,Vue Router会自动帮你拼接路径,以后只需要改路由规则就行。
用<router-link>组件跳转的代码:
<!-- BlogList.vue -->
<template>
<ul>
<li v-for="blog in blogList" :key="blog.id">
<!-- 用name属性传参,不需要手动拼接path -->
<router-link :to="{ name: 'BlogDetail', params: { blogId: blog.id } }">
{{ blog.title }}
</router-link>
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
// 假设这里从API获取了博客列表
const blogList = ref([
{ id: 1, title: 'Vue3 Composition API入门', content: '...' },
{ id: 2, title: 'Vue Router v4新特性解析', content: '...' }
])
</script>
用useRouter的push方法跳转的代码:
<!-- BlogList.vue (用push方法的版本) -->
<template>
<ul>
<li v-for="blog in blogList" :key="blog.id" @click="goToDetail(blog.id)">
{{ blog.title }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router' // 引入useRouter
const router = useRouter() // 初始化router实例
const blogList = ref([
{ id: 1, title: 'Vue3 Composition API入门', content: '...' },
{ id: 2, title: 'Vue Router v4新特性解析', content: '...' }
])
// 定义跳转方法
const goToDetail = (blogId) => {
// 同样推荐用name属性
router.push({ name: 'BlogDetail', params: { blogId } })
}
</script>
在目标组件(BlogDetail.vue)里获取params的方法:有两种,一种是用useRoute的params属性,一种是通过路由组件的props接收(后面会重点讲props的用法,这里先提一下用useRoute的)。
<!-- BlogDetail.vue -->
<template>
<div>
<h1>{{ currentBlog?.title || '加载中...' }}</h1>
<p>{{ currentBlog?.content || '暂无内容' }}</p>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router' // 引入useRoute
import { getBlogDetail } from '@/api/blog' // 假设这里有获取博客详情的API
const route = useRoute() // 初始化route实例
const currentBlog = ref(null)
onMounted(async () => {
// 从route.params里获取blogId
const blogId = route.params.blogId
if (blogId) {
currentBlog.value = await getBlogDetail(blogId)
}
})
</script>
动态路由匹配的params的坑
很多新手在这里踩的坑是:把动态路由匹配的params和“不依赖URL的params”搞混了,Vue Router v4里确实有“不依赖URL的params”,但它的使用条件非常苛刻:跳转时必须用name属性,不能用path;路由规则里不能有对应的占位符;刷新页面后这个params会直接消失,因为它根本没写在URL里。
举个错误的例子,很多新手可能会这样写:
// router/index.js(错误示例对应的路由规则,没有占位符)
const routes = [
{
path: '/blog-detail', // 这里没有:blogId的占位符
name: 'BlogDetail',
component: BlogDetail
}
]
<!-- BlogList.vue(错误示例的跳转代码) -->
<script setup>
const goToDetail = (blogId) => {
// 这里的params没有对应的占位符,属于不依赖URL的params
router.push({ name: 'BlogDetail', params: { blogId } })
}
</script>
这样写的话,跳转时地址栏会显示/blog-detail,BlogDetail.vue里也能获取到route.params.blogId,但一刷新页面,blogId就变成undefined了,因为它没存在URL里。除非你完全不需要刷新页面保留这个参数,也不需要别人复制链接访问,否则绝对不要用这种“不依赖URL的params”,Vue Router v4的官方文档里也明确说了,这种方式是“遗留功能”,未来可能会被移除,大家尽量别碰。
查询字符串query:写在URL问号后面的“可选/多值参数”
查询字符串query,就是URL里问号后面的部分,比如/blog-list?page=2&category=vue,这里的page=2和category=vue就是query参数,Vue Router会自动把这部分解析成一个对象,传给对应的组件。
什么时候用查询字符串query?
这种传参方式最适合传“可选的、多值的、可能会变化的”参数,比如商品列表页的筛选条件、页码、每页条数、搜索关键词——因为这些参数不是页面必须的(没有筛选条件就显示全部商品),而且数量可能很多,放路径里会让URL变得很长很乱,不符合美观和分享的要求,放问号后面就合适多了。
同样举个博客系统的例子,比如博客列表页支持按分类筛选和按页码翻页,路由规则可以这样写(不需要加占位符,因为query是独立于路径的):
// router/index.js
const routes = [
{
path: '/',
name: 'BlogList',
component: BlogList
},
{
path: '/blog/:blogId',
name: 'BlogDetail',
component: BlogDetail
}
]
跳转的时候,同样可以用<router-link>组件或useRouter的push方法,这里可以用path属性,也可以用name属性,不过用name属性同样更方便维护。
用<router-link>组件跳转的代码(比如点击分类标签):
<!-- BlogList.vue 的分类标签部分 -->
<template>
<div class="category-tags">
<span
v-for="cat in categories"
:key="cat.id"
:class="{ active: currentCategory === cat.id }"
>
<!-- 用path也可以,但推荐用name -->
<router-link :to="{ name: 'BlogList', query: { category: cat.id, page: 1 } }">
{{ cat.name }}
</router-link>
</span>
</div>
<!-- 分页器部分 -->
<div class="pagination">
<button
v-for="p in totalPages"
:key="p"
:class="{ active: currentPage === p }"
>
<router-link :to="{ name: 'BlogList', query: { ...route.query, page: p } }">
{{ p }}
</router-link>
</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// 假设这里有分类列表
const categories = ref([
{ id: 'all', name: '全部' },
{ id: 'vue', name: 'Vue' },
{ id: 'react', name: 'React' }
])
// 从route.query里获取当前分类和页码,注意query里的参数都是字符串类型
const currentCategory = computed(() => route.query.category || 'all')
const currentPage = computed(() => parseInt(route.query.page) || 1)
// 假设这里有总页数的计算
const totalPages = ref(10)
</script>
这里要注意一个小细节:query里的所有参数,不管你传的是数字、布尔值还是数组,Vue Router都会自动转换成字符串类型(或者数组里的元素都是字符串类型),所以在目标组件里获取到之后,一定要记得做类型转换,比如parseInt(page)或者JSON.parse(isActive)(不过布尔值一般不建议传query,传字符串'true'/'false'也行,或者直接不传表示false)。
用useRouter的push方法跳转的代码(比如点击搜索按钮):
<!-- BlogList.vue 的搜索部分 -->
<template>
<div class="search-box">
<input type="text" v-model="searchKeyword" placeholder="搜索博客" />
<button @click="searchBlog">搜索</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const searchKeyword = ref('')
const searchBlog = () => {
if (!searchKeyword.value.trim()) return
// 搜索时重置页码为1,保留其他已有的query参数(比如分类)
router.push({
name: 'BlogList',
query: {
...route.query, // 保留已有参数
keyword: searchKeyword.value,
page: 1
}
})
}
</script>
这里又有一个细节:用push方法传query时,如果用扩展运算符保留已有参数,要注意不要传undefined或null的参数,否则Vue Router会把它们转换成字符串'undefined'或'null',放在地址栏里很难看,所以最好在传之前过滤一下,
const searchBlog = () => {
if (!searchKeyword.value.trim()) return
// 过滤掉undefined或null的参数
const filteredQuery = Object.fromEntries(
Object.entries({
...route.query,
keyword: searchKeyword.value,
page: 1
}).filter(([_, value]) => value !== undefined && value !== null)
)
router.push({ name: 'BlogList', query: filteredQuery })
}
查询字符串query的坑
query的坑相对少一些,但也有两个需要注意的地方:
- 参数类型转换:刚才已经提过了,所有参数都是字符串,一定要记得转换;
- 复杂数据传递:比如你想传一个对象或者数组作为query参数,直接传的话Vue Router会调用
toString()方法,把对象变成[object Object],把数组变成逗号分隔的字符串,比如filter={color:red,size:L}会变成filter=%5Bobject+Object%5D,根本没法用,这时候怎么办?可以用JSON.stringify()把复杂数据转换成JSON字符串,再用encodeURIComponent()编码一下,防止特殊字符破坏URL结构,在目标组件里再用decodeURIComponent()解码,再用JSON.parse()解析回来。
举个例子,比如传一个筛选对象:
<!-- 跳转组件 -->
<script setup>
const goToFilteredList = () => {
const filter = { color: '红色', size: 'L', priceRange: [100, 500] }
// 转换并编码
const encodedFilter = encodeURIComponent(JSON.stringify(filter))
router.push({
name: 'ProductList',
query: { filter: encodedFilter }
})
}
</script>
<!-- 目标组件 -->
<script setup>
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// 解码并解析
const filter = computed(() => {
if (!route.query.filter) return {}
try {
return JSON.parse(decodeURIComponent(route.query.filter))
} catch (e) {
console.error('筛选参数解析失败', e)
return {}
}
})
</script>
不过这里也要注意:如果复杂数据太大,JSON字符串会很长,导致URL超过浏览器的长度限制(不同浏览器的限制不一样,一般是2KB到8KB之间),所以如果复杂数据太大,就不要用query传了,换用Vuex/Pinia或者sessionStorage。
通过路由组件的props传参,让代码更“组件化”的“软桥梁”
刚才的两种方式,都是在组件里直接引入useRoute来获取参数的,这样写虽然没问题,但有一个缺点:组件和Vue Router耦合度太高了,如果以后你想把这个组件复用在非路由的场景里(比如把BlogDetail.vue放在BlogList.vue的弹窗里展示),你就得修改组件的代码,去掉useRoute的部分,传入props来获取参数,有没有什么办法,让组件既能在路由场景里用,又能在非路由场景里用?当然有,那就是通过路由组件的props属性直接传参。
Vue Router v4的路由规则里,每个路由对象都有一个props属性,它可以是布尔值、对象、函数三种类型,不同的类型对应不同的传参方式,下面我们分别来讲。
props: true:把动态路由匹配的params直接传给组件的props
这是最简单的一种用法,只要在路由规则里把props设为true,Vue Router就会自动把动态路由匹配的params(注意是动态路由的params,不是不依赖URL的,也不是query),以相同的属性名传给组件的props。
举个刚才BlogDetail.vue的例子,修改一下路由规则:
// router/index.js
const routes = [
{
path: '/blog/:blogId',
name: 'BlogDetail',
component: BlogDetail,
props: true // 这里设为true
}
]
然后修改BlogDetail.vue,去掉useRoute,直接用props接收blogId:
<!-- BlogDetail.vue(修改后的版本,更组件化) -->
<template>
<div>
<h1>{{ currentBlog?.title || '加载中...' }}</h1>
<p>{{ currentBlog?.content || '暂无内容' }}</p>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getBlogDetail } from '@/api/blog'
// 直接用props接收,不需要useRoute了
const props = defineProps({
blogId: {
type: String, // 动态路由的params是字符串,和query一样
required: true
}
})
const currentBlog = ref(null)
onMounted(async () => {
// 直接用props.blogId
currentBlog.value = await getBlogDetail(props.blogId)
})
</script>
这样修改之后,BlogDetail.vue就和Vue Router解耦了,你可以随时把它放在非路由的场景里用,
<!-- BlogList.vue 的弹窗部分 -->
<template>
<div v-if="showDetailModal" class="modal">
<!-- 直接传blogId给BlogDetail.vue的props -->
<BlogDetail :blogId="selectedBlogId" />
<button @click="showDetailModal = false">关闭</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import BlogDetail from '@/views/BlogDetail.vue'
const showDetailModal = ref(false)
const selectedBlogId = ref(null)
const openDetailModal = (blogId) => {
selectedBlogId.value = blogId
showDetailModal.value = true
}
</script>
是不是方便多了?这也是官方推荐的用法之一,只要是动态路由匹配的必填标识,尽量用props: true的方式传。
props: 对象:把固定的静态数据传给组件的props
如果props是一个对象,Vue Router就会把这个对象的属性直接传给组件的props,不管params和query是什么,这种用法比较少见,一般用来传固定的静态配置数据,比如某个详情页的标题前缀、是否显示返回按钮等等。
举个例子:
// router/index.js
const routes = [
{
path: '/about',
name: 'About',
component: About,
props: {
pageTitle: '关于我们',
showBackButton: false
}
}
]
<!-- About.vue -->
<template>
<div>
<h1>{{ pageTitle }}</h1>
<button v-if="showBackButton" @click="router.back()">返回</button>
<p>这里是关于我们的内容...</p>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const props = defineProps({
pageTitle: String,
showBackButton: Boolean
})
</script>
props: 函数:把query、params甚至路由元信息组合起来传给组件的props
这是最灵活的一种用法,props可以是一个函数,这个函数接收一个route对象作为参数(就是useRoute()返回的那个对象),然后返回一个新的对象,这个新对象的属性会传给组件的props,用这种方式,你可以把query、params、路由元信息meta甚至其他数据组合起来,想传什么就传什么,而且同样能让组件和Vue Router解耦。
什么时候用props: 函数?
这种方式最适合需要同时传params和query,或者需要对参数做预处理(比如类型转换、复杂数据解析)的场景,比如刚才的博客列表页,需要传分类、页码、搜索关键词,而且需要把页码转换成数字,这时候就可以用props: 函数的方式。
修改一下BlogList.vue的路由规则:
// router/index.js
const routes = [
{
path: '/',
name: 'BlogList',
component: BlogList,
// props是一个函数,接收route对象
props: (route) => {
// 对参数做预处理
return {
category: route.query.category || 'all',
page: parseInt(route.query.page) || 1,
keyword: route.query.keyword || '',
// 还可以解析复杂数据
filter: route.query.filter ? JSON.parse(decodeURIComponent(route.query.filter)) : {}
}
}
},
{
path: '/blog/:blogId',
name: 'BlogDetail',
component: BlogDetail,
props: true
}
]
然后修改BlogList.vue,去掉useRoute,直接用props接收预处理好的参数:
<!-- BlogList.vue(修改后的版本,更组件化) -->
<template>
<div class="category-tags">
<span
v-for="cat in categories"
:key="cat.id"
:class="{ active: props.category === cat.id }"
>
<router-link :to="{ name: 'BlogList', query: { ...props, category: cat.id, page: 1 } }">
{{ cat.name }}
</router-link>
</span>
</div>
<div class="search-box">
<input type="text" v-model="localKeyword" placeholder="搜索博客" />
<button @click="searchBlog">搜索</button>
</div>
<!-- 博客列表、分页器等其他部分 -->
</template>
<script setup>
import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// 直接用props接收预处理好的参数
const props = defineProps({
category: String,
page: Number,
keyword: String,
filter: Object
})
// 本地的搜索关键词,用来和props.keyword同步
const localKeyword = ref(props.keyword)
// 监听props.keyword的变化,同步到localKeyword
watch(() => props.keyword, (newVal) => {
localKeyword.value = newVal
})
// 搜索方法,直接传预处理后的props(过滤掉undefined/null)
const searchBlog = () => {
const filteredQuery = Object.fromEntries(
Object.entries({
...props,
keyword: localKeyword.value.trim() || undefined, // 空字符串的话不传
page: 1
}).filter(([_, value]) => value !== undefined && value !== null)
)
router.push({ name: 'BlogList', query: filteredQuery })
}
</script>
这样修改之后,BlogList.vue也和Vue Router解耦了,而且所有的参数预处理都放在了路由规则里,组件里只需要直接用就行,代码更清晰,维护起来也更方便。
原生传参不够用?这些补充方案了解一下
刚才讲的三种原生传参方式(动态路由params、query、props),已经能解决大部分开发场景的问题了,但如果遇到敏感数据、超大数据、临时软状态这些场景,原生传参就不够用了,这时候可以用以下几种补充方案:
Vuex/Pinia:全局状态管理,最稳妥的复杂/敏感数据存储方案
Vuex是Vue2常用的全局状态管理库,Pinia是Vue3官方推荐的替代方案,它们都可以用来存储跨组件、跨路由的数据,不管是敏感数据、超大数据,还是临时软状态,都可以用它们存,刷新页面的话,数据会丢失,除非你配合vuex-persistedstate(Vuex)或pinia-plugin-persistedstate(Pinia)插件,把数据持久化到localStorage或sessionStorage里。
举个Pinia的例子(Vue3推荐用Pinia): 首先安装Pinia和持久化插件:
npm install pinia pinia-plugin-persistedstate
然后在main.js里引入:
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
import router from './router'
const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate) // 使用持久化插件
app.use(pinia)
app.use(router)
app.mount('#app')
然后创建一个store:
// stores/product.js
import { defineStore } from 'pinia'
export const useProductStore = defineStore('product', {
state: () => ({
// 敏感数据,比如用户ID
userId: null,
// 超大数据,比如商品的完整规格列表
fullSpecs: null,
// 临时软状态,比如列表页的滚动位置
scrollPosition: 0
}),
actions: {
setUserId(userId) {
this.userId = userId
},
setFullSpecs(specs) {
this.fullSpecs = specs
},
setScrollPosition(position) {
this.scrollPosition = position
}
},
// 持久化配置,默认存localStorage,可以改成sessionStorage
persist: {
key: 'product-store',
storage: sessionStorage, // 敏感数据建议存sessionStorage,关闭浏览器就清空
paths: ['userId', 'scrollPosition'] // 只持久化需要的属性,fullSpecs太大就不持久化了
}
})
然后在组件里使用:
<!-- ProductList.vue(跳转前存数据) -->
<script setup>
import { useProductStore } from '@/stores/product'
import { onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
const productStore = useProductStore()
const router = useRouter()
// 假设这里从API获取了商品的完整规格列表
const fullSpecs = ref([])
// 跳转前存滚动位置(onBeforeUnmount钩子,组件卸载前触发)
onBeforeUnmount(() => {
productStore.setScrollPosition(window.scrollY)
})
const goToDetail = (productId) => {
// 存敏感数据和超大数据
productStore.setUserId('123456')
productStore.setFullSpecs(fullSpecs.value.find(spec => spec.productId === productId))
// 跳转到详情页,只传productId这个必填标识(动态路由params)
router.push({ name: 'ProductDetail', params: { productId } })
}
</script>
<!-- ProductDetail.vue(跳转后取数据) -->
<script setup>
import { useProductStore } from '@/stores/product'
import { onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
const productStore = useProductStore()
const router = useRouter()
const props = defineProps({ productId: String })
onMounted(() => {
// 取敏感数据和超大数据
console.log('用户ID:', productStore.userId)
console.log('完整规格:', productStore.fullSpecs)
})
// 返回列表页前恢复滚动位置
onBeforeUnmount(() => {
if (router.currentRoute.value.name === 'ProductList') {
window.scrollTo(0, productStore.scrollPosition)
}
})
</script>
localStorage/sessionStorage:浏览器原生存储,简单但要注意清理
如果你不想引入Vuex/Pinia这样的全局状态管理库,也可以直接用浏览器原生的localStorage或sessionStorage来存储数据,localStorage的数据会永久保存(除非用户手动清空),sessionStorage的数据会在关闭浏览器标签页或窗口后清空。
用法很简单:
// 存数据
localStorage.setItem('key', 'value') // 只能存字符串,复杂数据要JSON.stringify
sessionStorage.setItem('key', JSON.stringify({ a: 1, b: 2 }))
// 取数据
const value = localStorage.getItem('key')
const complexValue = JSON.parse(sessionStorage.getItem('key') || '{}')
// 删数据
localStorage.removeItem('key')
sessionStorage.clear() // 清空所有
不过要注意:敏感数据不要存localStorage,因为localStorage是永久保存的,容易被窃取;超大数据也不要存localStorage/sessionStorage,因为它们的容量有限(一般是5MB左右);存了数据之后一定要记得清理,否则会污染其他页面。
路由元信息meta:传路由专属的静态配置数据
刚才讲props的时候,提到过可以用props: 函数的方式传路由元信息meta,meta是路由规则里的一个自定义属性,一般用来传路由专属的静态配置数据,比如页面标题、是否需要登录、是否需要缓存(配合keep-alive)等等。
举个例子:
// router/index.js
const routes = [
{
path: '/user-center',
name: 'UserCenter',
component: UserCenter,
meta: {
title: '个人中心',
requiresAuth: true, // 需要登录
keepAlive: true // 需要缓存
}
}
]
然后在路由守卫里判断是否需要登录:
// router/index.js
router.beforeEach((to, from, next) => {
// 设置页面标题
document.title = to.meta.title || '默认标题'
// 判断是否需要登录
if (to.meta.requiresAuth && !isLoggedIn()) {
next({ name: 'Login', query: { redirect: to.fullPath } })
} else {
next()
}
})
// 模拟登录状态判断
const isLoggedIn = () => {
return !!localStorage.getItem('token')
}
Vue3路由传参怎么选不踩坑?
最后给大家整理一个简单的选择指南,以后遇到传参问题,直接对照这个指南选就行:
- 必须的唯一标识,复制链接/刷新要保留:选动态路由匹配的params,尽量配合
props: true使用,让组件更解耦; - 可选的、多值的、可能会变化的参数,复制链接/刷新要保留,数据不大不敏感:选查询字符串query,尽量配合
props: 函数使用,做类型转换和复杂数据解析; - 固定的静态配置数据:选props: 对象或路由元信息meta;
- 敏感数据、超大数据、临时软状态,复制链接不需要:选Vuex/Pinia(推荐)或localStorage/sessionStorage,敏感数据和临时软状态建议存sessionStorage;
- 绝对不要用:不依赖URL的params(遗留功能,刷新丢数据,未来可能被移除)。
希望这篇文章能帮大家解决Vue3路由传参的所有问题,如果还有其他疑问,欢迎在评论区留言讨论。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网

