动态路由匹配 | Vue Router 参数化路由完全指南 🎯
掌握动态路由,让你的应用支持灵活的 URL 参数传递!
🌟 什么是动态路由匹配?
动态路由匹配是 Vue Router 的核心功能之一,它允许你使用参数化的路径来匹配多个相似的 URL。想象一下,你有一个用户详情页面,需要根据不同的用户 ID 显示不同的内容 - 这就是动态路由的完美应用场景!
💡 为什么需要动态路由?
传统的静态路由需要为每个页面定义固定的路径,而动态路由让你用一个路由配置就能处理无数个相似的页面,大大提升了开发效率和代码复用性。
🎮 基础用法 - 5分钟上手
📝 定义动态路由
javascript
import User from './components/User.vue'
const routes = [
// 🎯 动态字段以冒号开始
{ path: '/users/:id', component: User },
// 🔗 多个参数的路由
{ path: '/users/:username/posts/:postId', component: UserPost }
]🎨 在组件中使用参数
vue
<template>
<div class="user-profile">
<h1>👤 用户详情</h1>
<!-- ✨ 通过 $route.params 访问路由参数 -->
<div class="user-info">
<p><strong>用户 ID:</strong>{{ $route.params.id }}</p>
<p><strong>当前路径:</strong>{{ $route.path }}</p>
</div>
<!-- 🎯 根据参数加载不同内容 -->
<UserDetails :user-id="$route.params.id" />
</div>
</template>
<script>
export default {
name: 'User',
computed: {
userId() {
return this.$route.params.id
}
}
}
</script>🚀 URL 匹配示例
| 匹配模式 | 匹配路径 | route.params |
|---|---|---|
/users/:username | /users/john | { username: 'john' } |
/users/:username/posts/:postId | /users/john/posts/123 | { username: 'john', postId: '123' } |
/products/:category/:id | /products/electronics/456 | { category: 'electronics', id: '456' } |
⚡ 组合式 API 用法
vue
<script setup>
import { computed, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// 🎯 响应式获取参数
const userId = computed(() => route.params.id)
const username = computed(() => route.params.username)
// 👀 监听参数变化
watch(() => route.params.id, (newId, oldId) => {
console.log(`用户 ID 从 ${oldId} 变更为 ${newId}`)
// 🔄 重新加载用户数据
loadUserData(newId)
})
// 📊 获取完整的路由信息
const routeInfo = computed(() => ({
params: route.params, // 路由参数
query: route.query, // 查询参数
hash: route.hash, // 锚点
fullPath: route.fullPath // 完整路径
}))
</script>🔄 响应路由参数变化
⚠️ 重要概念:组件复用
当用户从 /users/john 导航到 /users/jane 时,同一个组件实例会被复用,而不是销毁重建。这意味着:
- ✅ 性能更好 - 避免了不必要的组件销毁和创建
- ⚠️ 生命周期钩子不会重新触发 - 需要手动监听参数变化
🎯 方法一:使用 watch 监听
vue
<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const userData = ref(null)
const loading = ref(false)
// 🔄 监听用户 ID 变化
watch(() => route.params.id, async (newId, oldId) => {
if (newId !== oldId) {
loading.value = true
try {
// 📊 重新获取用户数据
userData.value = await fetchUserData(newId)
} catch (error) {
console.error('获取用户数据失败:', error)
} finally {
loading.value = false
}
}
}, { immediate: true }) // 立即执行一次
async function fetchUserData(userId) {
// 🌐 模拟 API 调用
const response = await fetch(`/api/users/${userId}`)
return response.json()
}
</script>🛡️ 方法二:使用路由守卫
vue
<script setup>
import { ref } from 'vue'
import { onBeforeRouteUpdate } from 'vue-router'
const userData = ref(null)
const loading = ref(false)
// 🛡️ 路由更新前的守卫
onBeforeRouteUpdate(async (to, from) => {
// 🎯 只有当用户 ID 变化时才重新加载
if (to.params.id !== from.params.id) {
loading.value = true
try {
userData.value = await fetchUserData(to.params.id)
} catch (error) {
console.error('加载用户数据失败:', error)
// 🚫 可以取消导航
return false
} finally {
loading.value = false
}
}
})
</script>🎯 捕获所有路由 - 404 处理
🔍 通配符路由
javascript
const routes = [
// 📄 正常路由
{ path: '/', component: Home },
{ path: '/users/:id', component: User },
// 🎯 捕获所有未匹配的路由
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
},
// 🔗 特定前缀的通配符
{
path: '/user-:afterUser(.*)',
component: UserGeneric
}
]🎨 404 页面组件
vue
<template>
<div class="not-found-page">
<div class="error-container">
<h1>🔍 页面未找到</h1>
<p>抱歉,您访问的页面 <code>{{ $route.path }}</code> 不存在。</p>
<div class="suggestions">
<h3>您可以尝试:</h3>
<ul>
<li><router-link to="/">🏠 返回首页</router-link></li>
<li><router-link to="/users">👥 浏览用户列表</router-link></li>
<li><a href="#" @click="goBack">⬅️ 返回上一页</a></li>
</ul>
</div>
<!-- 🔍 搜索功能 -->
<div class="search-box">
<input
v-model="searchQuery"
placeholder="搜索您要找的内容..."
@keyup.enter="performSearch"
/>
<button @click="performSearch">🔍 搜索</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const searchQuery = ref('')
function goBack() {
router.back()
}
function performSearch() {
if (searchQuery.value.trim()) {
router.push(`/search?q=${encodeURIComponent(searchQuery.value)}`)
}
}
</script>🔄 编程式导航到 404
javascript
// 🎯 导航到 404 页面并保留原始路径信息
router.push({
name: 'NotFound',
params: {
pathMatch: route.path.substring(1).split('/')
},
query: route.query,
hash: route.hash
})🎨 高级匹配模式
🔧 可选参数
javascript
const routes = [
// 🎯 id 参数是可选的
{ path: '/users/:id?', component: User },
// 🔗 多个可选参数
{ path: '/posts/:year?/:month?/:day?', component: Archive }
]🔄 重复参数
javascript
const routes = [
// 📁 匹配多个路径段
{ path: '/files/*', component: FileExplorer },
// 🎯 一个或多个参数
{ path: '/tags/:tags+', component: TaggedPosts },
// 🔗 零个或多个参数
{ path: '/categories/:categories*', component: CategoryPosts }
]🎯 自定义正则表达式
javascript
const routes = [
// 📊 只匹配数字 ID
{ path: '/users/:id(\\d+)', component: User },
// 📧 匹配邮箱格式
{ path: '/profile/:email([\\w.-]+@[\\w.-]+\\.\\w+)', component: Profile },
// 🔗 匹配特定格式的 slug
{ path: '/posts/:slug([a-z0-9-]+)', component: Post }
]🎯 实战案例
🛍️ 电商网站路由
javascript
const routes = [
// 🏠 首页
{ path: '/', component: Home },
// 🛍️ 商品相关
{ path: '/products', component: ProductList },
{ path: '/products/:category', component: CategoryProducts },
{ path: '/products/:category/:id(\\d+)', component: ProductDetail },
// 👤 用户相关
{ path: '/user/:id(\\d+)', component: UserProfile },
{ path: '/user/:id(\\d+)/orders', component: UserOrders },
{ path: '/user/:id(\\d+)/orders/:orderId(\\d+)', component: OrderDetail },
// 🔍 搜索
{ path: '/search/:query?', component: SearchResults },
// 🚫 404 处理
{ path: '/:pathMatch(.*)*', component: NotFound }
]📱 博客系统路由
javascript
const routes = [
// 📝 文章相关
{ path: '/posts', component: PostList },
{ path: '/posts/:slug([a-z0-9-]+)', component: PostDetail },
{ path: '/posts/category/:category', component: CategoryPosts },
{ path: '/posts/tag/:tag', component: TaggedPosts },
// 👤 作者相关
{ path: '/authors/:username', component: AuthorProfile },
{ path: '/authors/:username/posts', component: AuthorPosts },
// 📅 归档
{ path: '/archive/:year(\\d{4})', component: YearlyArchive },
{ path: '/archive/:year(\\d{4})/:month(\\d{1,2})', component: MonthlyArchive }
]🎯 最佳实践
✅ 推荐做法
🎯 使用语义化的参数名
javascript// ✅ 好的命名 { path: '/users/:userId/posts/:postId', component: UserPost } // ❌ 避免的命名 { path: '/users/:id1/posts/:id2', component: UserPost }🔒 添加参数验证
javascript// ✅ 使用正则表达式验证 { path: '/users/:id(\\d+)', component: User }🛡️ 处理参数变化
javascript// ✅ 监听参数变化 watch(() => route.params.id, (newId) => { loadData(newId) })
⚠️ 注意事项
- 组件复用问题 - 记得监听参数变化
- 参数类型 - 路由参数始终是字符串
- 编码问题 - 特殊字符会被自动编码
- 性能考虑 - 避免在参数变化时进行重复的重量级操作
🚀 下一步学习
掌握了动态路由匹配后,建议继续学习:
🎉 恭喜!你已经掌握了动态路由匹配
现在你可以构建支持灵活参数传递的现代 Web 应用了!继续探索更多 Vue Router 的强大功能吧 🚀