路由参数是 Vue Router 中非常重要的功能,它允许我们根据不同的参数动态匹配路由,实现灵活的路由配置。Vue Router 支持多种类型的参数,包括路径参数、查询参数和哈希参数。
Vue Router 主要支持三种类型的参数:
| 参数类型 | URL示例 | 访问方式 | 特点 |
|---|---|---|---|
| 路径参数 (Params) | /user/123 |
route.params.id |
路径的一部分,必需参数 |
| 查询参数 (Query) | /search?q=vue&page=1 |
route.query.q |
可选的键值对参数 |
| 哈希参数 (Hash) | /about#section-2 |
route.hash |
页面内锚点定位 |
路径参数是 URL 路径的一部分,通常用于标识特定资源。
// router/index.js
const routes = [
// 基本动态路由
{
path: '/user/:id',
name: 'user',
component: () => import('@/views/User.vue')
},
// 多个参数
{
path: '/category/:categoryId/product/:productId',
name: 'product',
component: () => import('@/views/Product.vue')
},
// 可选参数
{
path: '/blog/:year?/:month?/:day?',
name: 'blog',
component: () => import('@/views/Blog.vue'),
// 当访问 /blog 时,year/month/day 都是 undefined
},
// 带正则约束的参数
{
path: '/user/:id(\\d+)', // 只匹配数字ID
name: 'userById',
component: () => import('@/views/UserById.vue')
},
{
path: '/file/:filename(.+)?', // 匹配任意文件名,包括扩展名
name: 'file',
component: () => import('@/views/File.vue')
},
// 重复参数
{
path: '/tags/:tags+',
name: 'tags',
component: () => import('@/views/Tags.vue')
// 匹配 /tags/vue, /tags/vue/router, /tags/vue/router/javascript
},
// 混合使用
{
path: '/search/:type/:query+',
name: 'search',
component: () => import('@/views/Search.vue')
// 匹配 /search/articles/vue, /search/users/john/doe
}
]
<!-- User.vue -->
<template>
<div>
<h2>用户详情</h2>
<!-- Options API -->
<div v-if="userId">
<p>用户ID: {{ userId }}</p>
<p>来自 $route: {{ $route.params.id }}</p>
</div>
<!-- Composition API -->
<div v-if="composUserId">
<p>Composition API 用户ID: {{ composUserId }}</p>
</div>
<!-- 多个参数示例 -->
<div v-if="$route.params.categoryId">
<p>分类ID: {{ $route.params.categoryId }}</p>
<p>产品ID: {{ $route.params.productId }}</p>
</div>
<!-- 重复参数示例 -->
<div v-if="$route.params.tags">
<h3>标签:</h3>
<ul>
<li v-for="tag in tagsArray" :key="tag">
{{ tag }}
</li>
</ul>
</div>
</div>
</template>
<script>
// Options API
export default {
name: 'User',
computed: {
userId() {
// 访问路径参数
return this.$route.params.id
},
tagsArray() {
// 处理重复参数(tags+ 会将多个标签合并为字符串)
const tags = this.$route.params.tags
return tags ? tags.split('/') : []
}
},
created() {
// 在生命周期中访问参数
console.log('用户ID:', this.$route.params.id)
console.log('完整参数对象:', this.$route.params)
// 根据参数加载数据
this.loadUserData(this.$route.params.id)
},
methods: {
loadUserData(userId) {
// 根据用户ID加载数据
console.log('加载用户数据:', userId)
}
},
// 监听参数变化(当同一组件复用时)
watch: {
'$route.params.id'(newId, oldId) {
if (newId !== oldId) {
console.log('用户ID变化:', oldId, '→', newId)
this.loadUserData(newId)
}
}
}
}
</script>
<!-- Composition API -->
<script setup>
import { computed, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// 访问路径参数
const composUserId = computed(() => route.params.id)
// 监听参数变化
watch(
() => route.params.id,
(newId, oldId) => {
if (newId !== oldId) {
console.log('用户ID变化 (Composition):', oldId, '→', newId)
loadUserData(newId)
}
}
)
// 立即执行一次
watch(
() => route.params.id,
(newId) => {
if (newId) {
loadUserData(newId)
}
},
{ immediate: true }
)
function loadUserData(userId) {
// 加载用户数据
console.log('加载用户数据 (Composition):', userId)
}
</script>
查询参数是 URL 中 ? 后面的键值对,用于传递可选参数。
// 1. 在模板中使用 router-link
<router-link :to="{
path: '/search',
query: { q: 'vue', page: 1, sort: 'relevance' }
}">
搜索 Vue
</router-link>
// 2. 编程式导航
import { useRouter } from 'vue-router'
const router = useRouter()
// 使用 path + query
router.push({
path: '/search',
query: {
q: 'vue',
page: 1,
sort: 'relevance',
filter: ['tag1', 'tag2'] // 数组参数会自动编码
}
})
// 使用 name + query
router.push({
name: 'search',
query: {
q: 'vue',
page: 1
}
})
// 3. 字符串形式(不推荐,容易出错)
router.push('/search?q=vue&page=1&sort=relevance')
// 4. 替换当前路由(不添加历史记录)
router.replace({
path: '/search',
query: { q: 'vue' }
})
// 5. 更新当前路由的查询参数(保持其他参数不变)
function updateQueryParam(key, value) {
router.push({
query: {
...router.currentRoute.value.query, // 保留现有参数
[key]: value // 更新或添加新参数
}
})
}
// 6. 删除查询参数
function removeQueryParam(key) {
const query = { ...router.currentRoute.value.query }
delete query[key]
router.push({ query })
}
// 7. 带查询参数的命名路由
<router-link :to="{
name: 'user',
params: { id: 123 },
query: { tab: 'profile', view: 'compact' }
}">
用户资料
</router-link>
<!-- Search.vue -->
<template>
<div>
<h2>搜索结果</h2>
<!-- 显示查询参数 -->
<div class="mb-3">
<p>搜索关键词: <strong>{{ searchQuery }}</strong></p>
<p>当前页码: <strong>{{ currentPage }}</strong></p>
<p>排序方式: <strong>{{ sortBy }}</strong></p>
<!-- 显示所有查询参数 -->
<div v-if="Object.keys(allQueryParams).length">
<h4>所有查询参数:</h4>
<ul>
<li v-for="(value, key) in allQueryParams" :key="key">
{{ key }}: {{ value }}
</li>
</ul>
</div>
</div>
<!-- 分页控件 -->
<div class="pagination">
<button
@click="goToPage(currentPage - 1)"
:disabled="currentPage <= 1"
>
上一页
</button>
<span>第 {{ currentPage }} 页</span>
<button @click="goToPage(currentPage + 1)">
下一页
</button>
</div>
</div>
</template>
<script>
// Options API
export default {
name: 'Search',
computed: {
// 访问查询参数
searchQuery() {
return this.$route.query.q || ''
},
currentPage() {
const page = parseInt(this.$route.query.page) || 1
return Math.max(1, page) // 确保页码不小于1
},
sortBy() {
return this.$route.query.sort || 'relevance'
},
allQueryParams() {
return this.$route.query
}
},
created() {
// 初始加载数据
this.performSearch()
},
watch: {
// 监听查询参数变化
'$route.query': {
handler(newQuery, oldQuery) {
console.log('查询参数变化:', oldQuery, '→', newQuery)
this.performSearch()
},
deep: true // 深度监听,因为 query 是对象
}
},
methods: {
performSearch() {
const { q, page, sort } = this.$route.query
console.log('执行搜索:', { q, page, sort })
// 调用 API 搜索...
},
goToPage(page) {
// 更新查询参数,触发重新搜索
this.$router.push({
query: {
...this.$route.query,
page: Math.max(1, page) // 确保页码有效
}
})
},
updateSort(sortType) {
this.$router.push({
query: {
...this.$route.query,
sort: sortType
}
})
}
}
}
</script>
<!-- Composition API -->
<script setup>
import { computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
// 访问查询参数
const searchQuery = computed(() => route.query.q || '')
const currentPage = computed(() => {
const page = parseInt(route.query.page) || 1
return Math.max(1, page)
})
const sortBy = computed(() => route.query.sort || 'relevance')
const allQueryParams = computed(() => route.query)
// 监听查询参数变化
watch(
() => route.query,
(newQuery, oldQuery) => {
console.log('查询参数变化 (Composition):', oldQuery, '→', newQuery)
performSearch()
},
{ deep: true }
)
// 立即执行一次
watch(
() => route.query,
() => performSearch(),
{ immediate: true }
)
function performSearch() {
const { q, page, sort } = route.query
console.log('执行搜索 (Composition):', { q, page, sort })
// 调用 API 搜索...
}
function goToPage(page) {
router.push({
query: {
...route.query,
page: Math.max(1, page)
}
})
}
function updateSort(sortType) {
router.push({
query: {
...route.query,
sort: sortType
}
})
}
</script>
哈希参数是 URL 中 # 后面的部分,通常用于页面内锚点定位。
// 1. 导航到带哈希的URL
router.push({
path: '/documentation',
hash: '#getting-started' // 定位到"快速开始"章节
})
// 或简写为
router.push('/documentation#getting-started')
// 2. 在模板中使用
<router-link :to="{ path: '/about', hash: '#team' }">
关于我们 - 团队介绍
</router-link>
// 3. 访问哈希参数
import { useRoute } from 'vue-router'
const route = useRoute()
console.log('当前哈希:', route.hash) // "#getting-started"
console.log('不带#的哈希:', route.hash.slice(1)) // "getting-started"
// 4. 监听哈希变化
watch(
() => route.hash,
(newHash) => {
if (newHash) {
// 滚动到对应元素
const elementId = newHash.slice(1)
const element = document.getElementById(elementId)
if (element) {
element.scrollIntoView({ behavior: 'smooth' })
}
}
}
)
// 5. 更新哈希(不触发页面重新加载)
function updateHash(newHash) {
router.push({ hash: newHash })
}
// 6. 移除哈希
function removeHash() {
// 方法1: 导航到不带哈希的相同路径
router.push({ path: route.path })
// 方法2: 使用 replace 避免历史记录
router.replace({ hash: null })
}
在实际应用中,经常需要对路由参数进行验证和类型转换。
// 1. 路由配置中的验证
const routes = [
{
path: '/user/:id',
component: User,
beforeEnter: (to, from, next) => {
const id = to.params.id
// 验证用户ID
if (!isValidUserId(id)) {
// 重定向到404或错误页面
next({ name: 'not-found' })
return
}
// 验证通过
next()
}
}
]
function isValidUserId(id) {
// 验证用户ID格式
return /^\d+$/.test(id) && parseInt(id) > 0
}
// 2. 在组件中进行参数验证和转换
export default {
computed: {
// 验证并转换参数
validatedUserId() {
const id = this.$route.params.id
// 检查是否存在
if (!id) {
return null
}
// 转换为数字
const numericId = parseInt(id)
// 验证是否为有效数字
if (isNaN(numericId) || numericId <= 0) {
console.error('无效的用户ID:', id)
return null
}
return numericId
},
// 安全的查询参数获取
safePageNumber() {
const page = this.$route.query.page
if (!page) return 1
const num = parseInt(page)
return isNaN(num) || num < 1 ? 1 : num
},
// 数组参数处理
tagArray() {
const tags = this.$route.query.tags
if (!tags) return []
// 处理字符串形式的数组参数
if (typeof tags === 'string') {
return tags.split(',').filter(tag => tag.trim())
}
// 处理数组形式的参数
if (Array.isArray(tags)) {
return tags.filter(tag => tag && tag.trim())
}
return []
}
},
watch: {
validatedUserId(newId, oldId) {
if (newId && newId !== oldId) {
this.loadUserData(newId)
}
}
},
created() {
// 创建时验证参数
if (!this.validatedUserId) {
this.$router.replace({ name: 'not-found' })
}
}
}
// 3. 使用 props 进行参数验证和传递
const routes = [
{
path: '/user/:id',
component: User,
props: (route) => ({
// 在路由级别进行验证和转换
id: validateAndConvertId(route.params.id),
// 传递查询参数作为 props
search: route.query.q || '',
page: parseInt(route.query.page) || 1
})
}
]
function validateAndConvertId(id) {
const num = parseInt(id)
return isNaN(num) || num <= 0 ? null : num
}
// 4. 在组件中接收验证后的 props
export default {
props: {
id: {
type: Number,
required: true,
validator: (value) => value > 0
},
search: {
type: String,
default: ''
},
page: {
type: Number,
default: 1,
validator: (value) => value >= 1
}
},
created() {
// props 已经经过验证
console.log('用户ID:', this.id)
console.log('搜索词:', this.search)
console.log('页码:', this.page)
if (!this.id) {
// 如果id无效(由于验证器,这种情况不会发生)
this.$router.replace({ name: 'not-found' })
}
}
}
| 特性 | 路径参数 (Params) | 查询参数 (Query) | 哈希参数 (Hash) |
|---|---|---|---|
| URL中的位置 | /path/:param |
/path?key=value |
/path#section |
| 是否必需 | 通常是必需的(可选参数除外) | 可选 | 可选 |
| SEO影响 | 影响大,每个参数都是独立页面 | 影响较小,被视为同一页面的不同状态 | 无影响 |
| 浏览器历史 | 每次变化都会创建新历史记录 | 每次变化都会创建新历史记录 | 通常不会创建新历史记录 |
| 典型用途 | 资源标识符(用户ID、文章ID) | 筛选、排序、分页参数 | 页面内锚点定位 |
| 可传递的数据类型 | 字符串 | 字符串、数字、数组、对象(需编码) | 字符串 |
| 数据量限制 | 受URL长度限制 | 受URL长度限制 | 受URL长度限制 |
| 安全性 | URL中可见 | URL中可见 | URL中可见 |
尝试不同的参数传递方式:
等待演示...
Vue Router 的路由参数功能强大且灵活,通过合理使用不同类型的参数,可以构建出用户体验良好、结构清晰的单页面应用。
关键要点回顾:
$route.params、$route.query、$route.hash 访问掌握路由参数的使用技巧,能够让你在开发 Vue.js 应用时更加得心应手。