基础
入门
路由的目的是建立 url 和组件之间的映射关系;当 url 发生变化时,组件也随之更新;
创建路由器实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { createMemoryHistory, createRouter } from "vue-router"
import HomeView from './HomeView.vue' import AboutView from './AboutView.vue'
const routes = [ { path: '/', component: Homeview }, { path: '/about', component: AboutView }, ]
const router = createRouter({ history: createMemoryHistory(), routes, })
|
history 用来记录 url 和路由的双向映射,这里用的是 createMemoryHistory,它会抛开浏览器的 url,完全自我管理;
如果想要跟浏览器的 url 保持关系,则可使用 createWebHistory 或者 createWebhashHistory;
注册路由器插件
创建好路由器实例化,可以用 app.use(router) 将其注册为插件;
1 2 3
| const app = createApp(); app.use(router); app.mount("#app")
|
注册该插件后,会完成以下几项工作:
- 注册需要的组件,如 RouterLink 和 RouterView
- 添加全局属性,如 $router 和 $route
- 添加全局组合式函数 useRouter 和 useRoute
- 解析初始路由
访问路由器和当前路由
在组合式 API 中,可使用 useRouter 和 useRoute 来访问路由器和当前路由;
动态路由匹配
动态匹配:用于将多个路径匹配到同一个组件
1 2 3 4 5 6
| import User from "./User.vue"
const routers = { { path: "/users/:id", component: User }, }
|
可在模板中使用 $route 或者 useRoute 来访问当前路径的参数,例如 $route.params.id
1 2 3 4 5
| <template> <div> User {{ $route.params.id }} </div> </template>
|
路由支持多个参数,例如 /users/:name/posts/:postId
响应路由参数的变化
当路由参数出现变化时,为提高性能,避免重新渲染,会直接复用原先的组件。这意味着组件不会重新创建,因此跟创建有关的 hook 函数例如 onMount 也不会重新执行;
可使用 watch 或者 onBeforeRouteUpdated 来监听变化,并执行相关的操作;
1 2 3 4 5 6 7 8
| <script setup> import { watch } from 'vue' import { useRoute } from 'vue-router' const route = useRoute() watch(() => route.params.id, (newId, oldId) => {...}) </script>
|
1 2 3 4 5 6 7
| <script setup> import { onBeforeRouteUpdated } from 'vue-router' onBeforeRouteUpdated(async(to, from) => { userData.value = await fetchUser(to.params.id); }) </script>
|
捕获所有路由
通过路径参数的正则表达式,可以匹配任意的路由
1 2 3 4
| const routes = [ { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound}, { path: '/user-:afteruser(.*)', component: UserGeneric }, ]
|
路由的匹配语法
在参数中自定义正则
当两个路径的前缀相同,只是参数的类型不同时,可使用正则来区分它们;
1 2 3 4 5
| const routes = [ { path: '/:orderId(\\d+)' }, { path: '/:productName' } ]
|
但我个人觉得更好的做法是修改前缀,这样更简单清晰
1 2 3 4
| const routes = [ { path: '/order/:orderId' }, { path: '/product/:name' }, ]
|
可重复的参数
星号 * 表示 0 个或多个
加号 + 表示 1 个或多个
1 2 3 4 5 6
| const routes = [ { path: '/:chapters+' }, { path: '/:chapters*'} ]
|
Sensitive 与 Strict 路由配置
默认情况下,路由是不区分大小写的,如需要区分,可添加 sensitive: true 和 strict: true 选项
可选参数
修饰符 ? 可用来标记可选参数,即 0 个或者 1 个;
1 2 3 4 5 6
| const routes = [ { path: '/users/:userId?' }, { path: '/users/:userId(\\d+)' } ]
|
嵌套路由
组件通常是嵌套的,这种嵌套关系也可以反映在 URL 上面。此时,可在路由中配置 children 子路由来标记这种嵌套关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const routes = [ { path: '/user/:userId', component: User, children: [ { path: 'profile', component: UserProfile }, { path: "posts", component: "UserPosts" }, { path: "", component: UserHome } ] } ]
|
命名路由
路由支持命名,只需将名称备注在 name 字段中即可;
1 2 3 4 5 6 7
| const routes = [ { path: "/user/:username", name: 'profile', component: User } ]
|
该名称可用于 router-link 中
1
| <router-link :to="{ name: 'profile' params: { username: 'erina' } }"></router-link>
|
建议使用命名路由,一来这样更方便维护,避免后续因为更改路径,导致很多地方需要跟着改动;二来好的名称也比路径也更容易理解;
路由的命名需要全局唯一,不然会出现冲突;
编程式导航
导航到不同的位置
router.push(…) 会跳转到新的页面,它同时会向页面 history 的栈中添加记录,这样当用户点击浏览器上面的按钮时,就会返回上一页;
点击 router-link 组件也会有相同的效果;
1
| <router-link :to="..."></router-link>
|
router.push 可以支持很多种格式,其参数可以是简单的 url,也可以是一个对象;
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| router.push('/users/eduardo')
router.push({ path: '/users/eduardo' })
router.push({ name: 'user', params: { username: 'eduardo' } })
router.push({ path: '/register', query: { plan: 'private' } })
router.push({ path: '/about', hash: '#team' })
|
替换当前位置
router.replace 会跳转新页面,作用与 router.push 相同,区别是直接替换当前路由在 history 中的位置,而不是 push;
横跨历史
在 history 栈中进行跳转,类似 window.history.go(n)
1 2 3 4
| router.go(1) router.go(-1) router.go(3) router.go(-3)
|
命名视图
给视图进行命名,可以让它们同时同级展示,而不是嵌套展示
1 2 3
| <router-view name="leftSidebar" /> <router-view /> <!-- 没有名称,默认为 default --> <router-view name="rightSidebar" />
|
当页面上存在多个视图时,需要给各视图映射相应的组件;
1 2 3 4 5 6 7 8 9 10 11 12 13
| const router = ({ history: createWebHashHistory(), routes: [ { path: "/", components: { default: Home, leftSidebar: LeftSidebar, rightSidebar: RightSidebar, } } ] })
|
命名视图也是支持嵌套滴;
重定向和别名
重定向
重写向:点击 A 路径,重定向跳转到 B 路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const routes = [{ path: "/home", redirect: "/" }]
const routes = [ { path: "/home", redirect: { name: "homepage" } } ]
const routes = [ { path: '/search/:searchText', redirect: to => { return { path: '/search', query: { q: to.params.searchText } } } } ]
|
相对重定向
1 2 3 4 5 6 7 8
| const routes = [ { path: '/users/:userId/posts', redirect: to => { return 'profile' } } ]
|
别名
通过别名,可将任意指定的 url 匹配到相应的组件,而不会受到嵌套结构的限制;
路由组件传参
如果在组件中读取 $route 的参数,那么意味着使用该组件,将会与 url 强绑定;组件的复用范围受到了很大的限制;解决方法就是不使用 $route,而是给组件传递参数;
1 2 3 4 5 6
| <!-- 传统的写法,组件与url紧密耦合 --> <template> <div> User {{ $route.params.id}} </div> </template>
|
1 2 3 4 5 6 7 8 9 10 11 12
| <!-- 新的写法, id 引用 props,由外部传入,而不是读取路由 --> <script setup> defineProps({ id: String, // 声明 props 参数 }) </script>
<template> <div> User {{ id }} </div> </template>
|
1 2 3 4
| const routes = [ { path: "/user/:id", component: User, props: true } ]
|
如果路由映射了多个命名视图,那么需要为每个视图单独备注是否启用 props
1 2 3 4 5 6 7 8 9 10
| const routes = [ { path: "/user/:id", components: { default: User, sidebar: Sidebar }, props: { default: true, sidebar: false, } } ]
|
另外 props 还支持对象或函数类型;
匹配当前路由的链接
有时候多个 router-link 在页面上面会以列表的形式出现,此时经常用不同的颜色,来标识当前激活的 link;
此时需要有一个方法来判断当前处于激活状态的是哪个链接;
不同的历史记录模式
有三种历史模式
- Hash 模式:会在 URL 上面添加 # 符号,好处是用户重新刷新页面也不要紧,能够正常处理;
- HTML 模式:URL 跟普通网页的 URL 一模一样,缺点当用户刷新时,会向服务器发送页面请求,需要服务器有相应的处理,不然会出现 404
- Memory 模式:URL 只保存在内存,不在浏览器的 URL 上面体现,缺点是无法使用浏览器的前进和后退,因为没有页面栈;