64. 路由参数与查询参数

📖 概述

路由参数和查询参数是Vue Router中用于在路由间传递数据的重要机制。路由参数用于标识特定资源,通常出现在URL路径中;查询参数用于过滤、排序或配置资源,通常出现在URL末尾的查询字符串中。本集将深入讲解Vue Router 4.x中路由参数和查询参数的定义、配置、获取和使用,帮助你掌握在Vue 3应用中通过路由传递数据的完整技能。

✨ 核心知识点

1. 路由参数基础

什么是路由参数

  • 路由参数是URL路径中的动态部分,用于标识特定资源
  • 路由参数以冒号(:)开头,如/user/:id中的:id
  • 路由参数的值会被解析到route.params对象中
  • 适用于标识唯一资源,如用户ID、产品ID等

配置路由参数

// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

const routes: Array<RouteRecordRaw> = [
  {
    path: '/user/:id',
    name: 'user',
    component: () => import('../views/UserView.vue')
  },
  {
    path: '/product/:category/:id',
    name: 'product',
    component: () => import('../views/ProductView.vue')
  },
  {
    // 可选参数,以?结尾
    path: '/article/:id?',
    name: 'article',
    component: () => import('../views/ArticleView.vue')
  },
  {
    // 零或多个参数,以*结尾
    path: '/files/*',
    name: 'files',
    component: () => import('../views/FilesView.vue')
  },
  {
    // 一个或多个参数,以+结尾
    path: '/tags/:tag+',
    name: 'tags',
    component: () => import('../views/TagsView.vue')
  }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

export default router

2. 获取路由参数

在组件中获取路由参数

1. 使用Composition API
<script setup lang="ts">
import { useRoute } from 'vue-router'

const route = useRoute()

// 获取路由参数
const userId = route.params.id as string
const category = route.params.category as string
const tag = route.params.tag as string

// 响应式获取参数
console.log(route.params) // { id: '123' }
</script>
2. 使用Options API
<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  computed: {
    userId() {
      return this.$route.params.id
    }
  },
  watch: {
    '$route.params'(newParams, oldParams) {
      // 监听路由参数变化
      console.log('参数变化:', newParams, oldParams)
    }
  }
})
</script>

路由参数的类型转换

<script setup lang="ts">
import { useRoute, watch } from 'vue-router'

const route = useRoute()

// 类型转换
const userId = parseInt(route.params.id as string)
const isActive = route.params.active === 'true'

// 监听参数变化并转换类型
watch(
  () => route.params,
  (newParams) => {
    const id = parseInt(newParams.id as string)
    // 使用转换后的参数
    fetchUser(id)
  },
  { deep: true }
)

async function fetchUser(id: number) {
  // 异步请求用户数据
}
</script>

3. 查询参数

什么是查询参数

  • 查询参数是URL末尾的键值对,以?开头,如/search?q=vue&amp;sort=desc
  • 查询参数会被解析到route.query对象中
  • 适用于过滤、排序、分页等场景
  • 查询参数是可选的,不影响路由匹配

配置和使用查询参数

// 编程式导航传递查询参数
router.push({ 
  path: '/search', 
  query: { q: 'vue', sort: 'desc' } 
})

// 声明式导航传递查询参数
<router-link :to="{ path: '/search', query: { q: 'vue' } }">搜索</router-link>

在组件中获取查询参数

<script setup lang="ts">
import { useRoute, watch } from 'vue-router'

const route = useRoute()

// 获取查询参数
const searchQuery = route.query.q as string
const sortOrder = route.query.sort as string
const page = parseInt(route.query.page as string || '1')

// 监听查询参数变化
watch(
  () => route.query,
  (newQuery, oldQuery) => {
    console.log('查询参数变化:', newQuery, oldQuery)
    // 执行搜索或过滤操作
    performSearch(newQuery)
  },
  { deep: true }
)

function performSearch(query: Record<string, string>) {
  // 执行搜索逻辑
}
</script>

4. 路由参数与查询参数的对比

特性 路由参数 查询参数
语法 /user/:id /search?q=vue
位置 URL路径中 URL末尾
必填性 路由匹配时必填(除非配置为可选) 可选
唯一性 用于标识唯一资源 用于过滤、排序等
历史记录 改变路由参数会创建新的历史记录 改变查询参数会创建新的历史记录
类型 字符串 字符串(需要手动转换)
适用场景 用户详情、产品详情等 搜索、过滤、分页等

5. 高级路由参数技巧

1. 路由参数的默认值

// 使用props传递默认值
const routes: Array<RouteRecordRaw> = [
  {
    path: '/user/:id',
    name: 'user',
    component: UserView,
    props: (route) => ({
      id: route.params.id || 'default'
    })
  }
]

2. 路由参数的验证

// 在组件中验证路由参数
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

const userId = parseInt(route.params.id as string)

// 验证参数
if (isNaN(userId) || userId <= 0) {
  // 参数无效,重定向到404页面
  router.push('/404')
}
</script>

3. 路由参数的解构

<script setup lang="ts">
import { useRoute } from 'vue-router'

const route = useRoute()

// 解构路由参数
const { id, category } = route.params as {
  id: string
  category: string
}

// 解构查询参数
const { q, sort, page } = route.query as {
  q: string
  sort: string
  page: string
}
</script>

6. 路由参数与组件通信

1. 使用props传递路由参数

// 配置路由,将参数作为props传递
const routes: Array<RouteRecordRaw> = [
  {
    path: '/user/:id',
    name: 'user',
    component: UserView,
    props: true // 将路由参数作为props传递给组件
  },
  {
    path: '/search',
    name: 'search',
    component: SearchView,
    // 将查询参数作为props传递
    props: (route) => ({ 
      query: route.query.q, 
      sort: route.query.sort 
    })
  }
]
<!-- UserView.vue -->
<script setup lang="ts">
interface Props {
  id: string
}

const props = defineProps<Props>()

// 直接使用props.id,无需访问route.params
console.log(props.id)
</script>

2. 路由参数与响应式数据

<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const user = ref<User | null>(null)

// 监听路由参数变化,更新响应式数据
watch(
  () => route.params.id,
  async (newId) => {
    if (newId) {
      user.value = await fetchUser(newId as string)
    }
  },
  { immediate: true } // 立即执行
)

async function fetchUser(id: string) {
  // 异步请求用户数据
}
</script>

7. 动态路由与参数

1. 动态生成路由

// 根据权限动态生成路由
function generateRoutes(permissions: string[]) {
  const dynamicRoutes: Array<RouteRecordRaw> = []
  
  if (permissions.includes('admin')) {
    dynamicRoutes.push({
      path: '/admin/users/:id',
      name: 'admin-user',
      component: () => import('../views/AdminUserView.vue')
    })
  }
  
  return dynamicRoutes
}

2. 动态修改路由参数

// 动态修改路由参数,不触发导航
router.replace({
  ...router.currentRoute.value,
  params: {
    ...router.currentRoute.value.params,
    id: 'new-id'
  }
})

8. 路由参数与导航守卫

1. 在导航守卫中使用路由参数

// src/router/index.ts
router.beforeEach((to, from, next) => {
  // 检查路由参数
  if (to.name === 'user' && !to.params.id) {
    next({ name: '404' })
    return
  }
  
  // 验证参数格式
  if (to.name === 'product') {
    const id = parseInt(to.params.id as string)
    if (isNaN(id)) {
      next({ name: '404' })
      return
    }
  }
  
  next()
})

2. 组件内守卫处理参数变化

<script setup lang="ts">
import { onBeforeRouteUpdate } from 'vue-router'

onBeforeRouteUpdate((to, from, next) => {
  // 路由参数更新时处理
  const newId = to.params.id as string
  const oldId = from.params.id as string
  
  if (newId !== oldId) {
    // 参数变化,重新获取数据
    fetchData(newId)
  }
  
  next()
})

function fetchData(id: string) {
  // 获取数据
}
</script>

📝 最佳实践

  1. 使用命名路由传递参数

    • 优先使用命名路由+params的方式传递参数
    • 避免使用字符串路径拼接,减少错误
  2. 路由参数类型安全

    • 始终对路由参数进行类型检查和转换
    • 使用TypeScript接口定义参数类型
  3. 监听路由参数变化

    • 使用watch监听参数变化,及时更新数据
    • 避免在组件初始化时只获取一次参数
  4. 使用props传递参数

    • 将路由参数作为props传递给组件,提高组件复用性
    • 使组件更独立,便于测试
  5. 参数验证

    • 在导航守卫或组件中验证路由参数的有效性
    • 对无效参数进行处理,如重定向到404页面
  6. 合理使用路由参数和查询参数

    • 资源标识使用路由参数
    • 过滤、排序等使用查询参数
  7. 避免过度使用动态路由

    • 动态路由过多会增加路由匹配的复杂度
    • 考虑使用查询参数替代部分动态路由
  8. 路由参数的默认值

    • 为可选参数提供合理的默认值
    • 使用props传递默认值,提高组件可用性

💡 常见问题与解决方案

  1. 路由参数变化但组件不更新

    • 原因:组件已被缓存或未监听参数变化
    • 解决方案:
      • 使用watch监听路由参数变化
      • 使用onBeforeRouteUpdate守卫处理参数更新
      • 确保组件没有被keep-alive缓存,或使用activated钩子
  2. 路由参数类型错误

    • 原因:未对参数进行类型转换
    • 解决方案:
      • 使用类型断言或类型转换
      • 在组件中验证参数类型
      • 使用TypeScript接口定义参数类型
  3. 查询参数丢失

    • 原因:使用命名路由时,query参数不会自动保留
    • 解决方案:
      • 在导航时显式传递query参数
      • 使用router.push({ ...to, query: { ...to.query, newParam: &#39;value&#39; } })
  4. 路由参数包含特殊字符

    • 原因:URL中包含空格、中文等特殊字符
    • 解决方案:
      • 使用encodeURIComponent编码参数
      • 使用decodeURIComponent解码参数
  5. 路由匹配优先级问题

    • 原因:路由配置顺序不当,导致优先级错误
    • 解决方案:
      • 将更具体的路由放在前面
      • 避免通配符路由在前面

📚 进一步学习资源

🎯 课后练习

  1. 基础练习

    • 配置带路由参数的路由
    • 在组件中获取和使用路由参数
    • 传递和获取查询参数
  2. 进阶练习

    • 实现路由参数的类型转换和验证
    • 使用props传递路由参数
    • 监听路由参数变化,更新响应式数据
  3. 实战练习

    • 构建一个产品详情页面,使用路由参数传递产品ID
    • 实现一个搜索页面,使用查询参数进行过滤和排序
    • 构建一个分页系统,使用查询参数传递页码
  4. 性能优化练习

    • 优化路由参数变化时的数据获取
    • 实现路由参数的缓存策略
    • 测试不同路由参数配置的性能差异

通过本集的学习,你已经掌握了Vue Router 4.x中路由参数和查询参数的定义、配置、获取和使用,以及路由参数与组件通信、导航守卫的结合使用。在实际项目中,合理运用路由参数和查询参数,能够构建出功能完整、用户体验良好的单页应用。下一集我们将深入学习命名路由与命名视图,进一步提升Vue 3路由系统的使用能力。

« 上一篇 Vue3 + TypeScript 系列教程 - 第63集:编程式导航方法大全 下一篇 » Vue3 + TypeScript 系列教程 - 第65集:命名路由与命名视图