动态路由

动态路由允许在路由路径中使用参数,实现灵活的路由匹配。当不同路由映射到同一个组件时,动态路由特别有用。

基本用法

定义动态路由

使用冒号 : 标记动态片段:

const routes = [
  { path: '/user/:id', component: User }
]

获取参数

在组件中通过 $route.params 获取:

export default {
  created() {
    console.log(this.$route.params.id)
  }
}

使用 props 解耦

将路由参数作为组件 props 传入:

const routes = [
  { path: '/user/:id', component: User, props: true }
]
export default {
  props: {
    id: {
      type: [String, Number],
      required: true
    }
  },
  created() {
    console.log(this.id)
  }
}

多个参数

多个动态片段

const routes = [
  { path: '/user/:userId/post/:postId', component: Post }
]
export default {
  created() {
    const { userId, postId } = this.$route.params
    console.log(`用户 ${userId} 的文章 ${postId}`)
  }
}

命名路由

const routes = [
  {
    path: '/user/:userId/post/:postId',
    name: 'post',
    component: Post
  }
]
<router-link :to="{ name: 'post', params: { userId: 1, postId: 2 } }">
  查看文章
</router-link>

参数类型

字符串参数(默认)

{ path: '/user/:id', component: User }

/user/123  → id = "123"
/user/abc  → id = "abc"

数字参数

使用正则限制为数字:

{ path: '/user/:id(\\d+)', component: User }

/user/123  → 匹配
/user/abc  → 不匹配

可选参数

{ path: '/user/:id?', component: User }

/user       → id = undefined
/user/123   → id = "123"

可重复参数

{ path: '/user/:id+', component: User }

/user/1           → id = "1"
/user/1/2/3       → id = ["1", "2", "3"]
{ path: '/user/:id*', component: User }

/user             → id = []
/user/1           → id = ["1"]
/user/1/2/3       → id = ["1", "2", "3"]

正则匹配

自定义正则

const routes = [
  { path: '/user/:id(\\d+)', component: User },
  { path: '/user/:name([a-z]+)', component: UserByName }
]

复杂正则

const routes = [
  { path: '/file/:path(.*)', component: File },
  { path: '/lang/:lang(en|zh|ja)', component: Lang }
]

捕获所有路由

404 路由

const routes = [
  { path: '/user/:id', component: User },
  { path: '*', component: NotFound }
]

history 模式

const routes = [
  { path: '/:pathMatch(.*)*', component: NotFound }
]

参数变化响应

问题:参数变化组件不更新

当从 /user/1 导航到 /user/2 时,复用同一个组件,不会触发生命周期:

export default {
  created() {
    console.log('只执行一次')
  }
}

解决方案一:watch $route

export default {
  watch: {
    '$route'(to, from) {
      if (to.params.id !== from.params.id) {
        this.fetchUser()
      }
    }
  },
  methods: {
    fetchUser() {
      const id = this.$route.params.id
    }
  }
}

解决方案二:beforeRouteUpdate

export default {
  beforeRouteUpdate(to, from, next) {
    if (to.params.id !== from.params.id) {
      this.fetchUser()
    }
    next()
  }
}

解决方案三:使用 key

<router-view :key="$route.fullPath"></router-view>

Query 参数

定义路由

const routes = [
  { path: '/search', component: Search }
]

传递 Query

<router-link :to="{ path: '/search', query: { q: 'vue', page: 1 } }">
  搜索
</router-link>
this.$router.push({
  path: '/search',
  query: { q: 'vue', page: 1 }
})

获取 Query

export default {
  created() {
    console.log(this.$route.query.q)
    console.log(this.$route.query.page)
  }
}

URL 格式

/search?q=vue&page=1

Hash 参数

获取 Hash

export default {
  created() {
    console.log(this.$route.hash)
  }
}

URL 格式

/user#section1

动态添加路由

router.addRoutes

router.addRoutes([
  { path: '/admin', component: Admin }
])

router.addRoute(Vue Router 3.5+)

router.addRoute({
  path: '/admin',
  component: Admin
})

router.addRoute('parent', {
  path: 'child',
  component: Child
})

动态路由示例

const router = new VueRouter({
  routes: [
    { path: '/login', component: Login },
    { path: '/403', component: Forbidden }
  ]
})

async function initRoutes() {
  const permissions = await fetchPermissions()
  
  const dynamicRoutes = permissions.map(perm => ({
    path: perm.path,
    component: () => import(`@/views/${perm.component}.vue`)
  }))
  
  router.addRoutes(dynamicRoutes)
}

路由匹配规则

匹配优先级

路由按定义顺序匹配:

const routes = [
  { path: '/user/profile', component: UserProfile },
  { path: '/user/:id', component: User }
]

/user/profile → 匹配第一个
/user/123     → 匹配第二个

匹配结果

const route = router.resolve('/user/123')
console.log(route.route.params)

实战示例

用户详情页

const routes = [
  {
    path: '/user/:id',
    name: 'user',
    component: User,
    props: true,
    children: [
      {
        path: 'posts',
        name: 'user-posts',
        component: UserPosts
      },
      {
        path: 'settings',
        name: 'user-settings',
        component: UserSettings
      }
    ]
  }
]
export default {
  props: {
    id: [String, Number]
  },
  computed: {
    user() {
      return this.$store.state.users[this.id]
    }
  },
  watch: {
    id: {
      immediate: true,
      handler(newId) {
        this.$store.dispatch('fetchUser', newId)
      }
    }
  }
}

文章详情页

const routes = [
  {
    path: '/post/:year(\\d{4})/:month(\\d{2})/:slug',
    name: 'post',
    component: Post,
    props: true
  }
]
export default {
  props: {
    year: String,
    month: String,
    slug: String
  },
  computed: {
    post() {
      return this.$store.getters.getPostBySlug(
        this.year,
        this.month,
        this.slug
      )
    }
  }
}

小结

动态路由要点:

  1. 参数定义:使用 :param 语法定义动态片段
  2. 参数获取$route.paramsprops: true
  3. 参数类型:可选、可重复、正则限制
  4. 参数变化:watch $route 或 beforeRouteUpdate
  5. 动态添加:addRoutes 或 addRoute