Vue Router 学习笔记-扩展
# Vue Router 学习笔记-扩展
这部分记录了VueRouter的扩展部分,覆盖了路由的众多场景。
# 路由名匹配
# 组件名匹配
根据name或path来匹配
const routes = [
{ path: '/', component: Home },
{ name: 'about', path: '/about', component: About },
]
# 动态路由匹配
为了将 /users/johnny
和 /users/jolyne
都匹配到 User页面,引入动态路由匹配的概念,用到了params
参数。
注意:**导航函数如果传入了path,则会忽略params。**因为path本身就是路径,params是用于动态路由中组成path的一部分(例如path: "/a/:userId"
中的userId这个动态参数),所以path和params不能互相传。(params可通过this.$route.params
获取)
const routes = [
// 动态字段以冒号开始,id需要做为 params
{ name: "PageA", path: '/users/:id', component: User },
]
// 注意 params 不能与 path一起使用
router.push({
name: "PageA",
params: {
id: 5,
}
});
# 路由正则匹配
为了适应更多复杂的场景,引入了正则的写法,具体参考:路由的匹配语法 (opens new window)
# 嵌套路由
解决一个组件下引入不同组件的写法,核心是 children 的写法。
例如下面的嵌套组件中,在User下需要嵌套 Profile 和 Posts,
/user/johnny/profile /user/johnny/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
// User组件中使用 <router-view>
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`,
}
// 在 User的路由配置中,使用 children,是一个数组,单个结构同路由的写法
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 <router-view> 内部
path: 'posts',
component: UserPosts,
},
],
},
]
# 命名路由
为组件设置 name 属性,后续即可使用这个name进行路由跳转。
为组件设置 name 属性,设置后可以使用 params属性,即 params 必须与name配套使用,且params是自动解码和编码的
# 命名视图
当同级需要展示多个视图而不是嵌套的时候,需要使用命名视图。
命名视图的核心是components
配置。
// 页面有 sidebar 和 main 两个视图,注意如果 router-view 没有设置名字,那么默认为 default
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
// 路由配置中使用 components
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
# 重定向和别名
在路由配置中,可以设置重定向和别名。
重定向使用redirect
// 重定向,redirect 可使用字符串、命名路由、动态函数
// 从 /home 重定向到 /
const routes = [{ path: '/home', redirect: '/' }]
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
const routes = [
{
// /search/screens -> /search?q=screens
path: '/search/:searchText',
redirect: to => {
// 方法接收目标路由作为参数
// return 重定向的字符串路径/路径对象
return { path: '/search', query: { q: to.params.searchText } }
},
},
{
path: '/search',
// ...
},
]
可以给组件起个别名,别名使用alias
// / 的别名是 /home,访问 /home 与 / 一样
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
# 路由组件传参
参数除了可以通过 $route.params 传递,还可以把 params的参数都设置为 props,即在使用params时,多了一个设置props的选项。
关键字为:props
把params设置为props有三种模式:
- 布尔模式,设置
props: true
,直接将route.params 将设置为 组件的props - 对象模式,设置props为Object,适合props为静态值的情况,注意当使用此模式时,组件内props会忽略params参数
- 函数模式,设置 props 为函数,适合将params值与静态值结合的情况
// 布尔模式
{
path: "/b/:id",
name: "PageB",
component: () => import("@/page/PageB.vue"),
props: true,
}
// 对象模式
{
path: "/b/:id",
name: "PageB",
component: () => import("@/page/PageB.vue"),
props: {
objectModel: 123,
},
}
// 函数模式
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
# 历史记录模式
单页面应用SPA,当需要做到改变路由,但是不请求后端时,基于浏览器的特性有两种实现方式
- Hash 模式,基于浏览器不会将#后面的发送到后端的特性(即url中带 #,例如 baidu.com/#/abc,该链接后#后面的不会发送到后端),当#后面改变时,监听 hashchange 事件,即可根据改变的url找到对应的组件。
- H5模式,是基于H5新增的两个方法:pushState 和 replaceState,使用这两个方法改变浏览器历史堆栈,不会触发后端请求。但是如果用户手动刷新,就会匹配完整url,因此需要后端服务设置 重定向
两个模式的区别:
- hash模式中url一定会带#,H5模式不会是标准的url,当url众不允许出现 # 时,就需要使用 H5模式
- h5模式下,当用户刷新页面,会将整个url发送到后端,会出现404,需要后端做对应重定向配置
具体参考:Vue-router 中hash模式和history模式的区别 (opens new window)
# 路由元信息
字段meta
,用于将 任意附加信息 附加到路由上,如权限控制等,可通过 $route.meta 获取到所有meta信息。
// 路由配置内设置 meta 信息,可添加任意信息,代码内可通过 $route.meta获取,守卫内可通过to.meta 或 from.meta获取
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true }
},
{
path: ':id',
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false }
}
]
}
]
# 数据获取
组件也可以在导航守卫函数内获取数据,然后使用next函数进行跳转
# 过渡动效
# 滚动行为
当组件切换时,可使用 scrollBehavior
设置组件切换后 页面滚动的位置。scrollBehavior 函数返回值类似 Window.scroll() 函数。
const router = createRouter({
history: createWebHashHistory(),
routes: [...],
// scrollBehavior 函数返回值类似 Window.scroll() 函数
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
// 始终滚动到顶部
return { top: 0 }
// 始终在元素 #main 上方滚动 10px
return {
// 也可以这么写
// el: document.getElementById('main'),
el: '#main',
top: -10,
}
// 模拟 “滚动到锚点” 的行为
return {
el: to.hash,
}
// 支持 behavior 参数
return {
el: to.hash,
behavior: 'smooth',
}
// 延迟滚动,需要返回 promise函数
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ left: 0, top: 0 })
}, 500)
})
}
})
# 路由懒加载
Vue Router 支持开箱即用的动态导入 (opens new window),尽量使用动态导入
// 静态导入
import UserDetails from './views/UserDetails.vue'
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: UserDetails }],
})
// 动态导入
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: () => import('./views/UserDetails.vue' }],
})
// component 也支持配置 promise的函数,可进行更复杂的配置
const UserDetails = () =>
Promise.resolve({
/* 组件定义 */
})
# 把组件按组分块
webpack,需要 Webpack > 2.4,将多个组件打包到同个异步块(chunk)内,使用:webpackChunkName
const UserDetails = () =>
import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
const UserDashboard = () =>
import(/* webpackChunkName: "group-user" */ './UserDashboard.vue')
const UserProfileEdit = () =>
import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')
Vite
在vite.config.js文件中使用:rollupOptions
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
// https://rollupjs.org/guide/en/#outputmanualchunks
output: {
manualChunks: {
'group-user': [
'./src/UserDetails',
'./src/UserDashboard',
'./src/UserProfileEdit',
],
},
},
},
})
# 扩展 RouterLink
可以封装 router-link 形成自定义的 RouterLink,一般用于 需要给RouterLink添加更多额外的props
// 使用 AppLink 封装 RouterLink
<template>
<AppLink
v-bind="$attrs"
class="inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 focus:outline-none transition duration-150 ease-in-out hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
active-class="border-indigo-500 text-gray-900 focus:border-indigo-700"
inactive-class="text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300"
>
<slot />
</AppLink>
</template>
// AppLink
import { RouterLink, useLink } from 'vue-router'
export default {
name: 'AppLink',
props: {
// 如果使用 TypeScript,请添加 @ts-ignore
...RouterLink.props,
inactiveClass: String,
},
setup(props) {
// `props` 包含 `to` 和任何其他可以传递给 <router-link> 的 prop
const { navigate, href, route, isActive, isExactActive } = useLink(props)
// profit!
return { isExternalLink }
},
}
# 导航故障
导航函数是异步的,当触发导航行为时,会有以下情况可能导致用户停留在当前页面:
- 用户已经位于他们正在尝试导航到的页面
- 一个导航守卫 (opens new window)通过调用
return false
中断了这次导航 - 当前的导航守卫还没有完成时,一个新的导航守卫会出现了
- 一个导航守卫 (opens new window)通过返回一个新的位置,重定向到其他地方 (例如,
return '/login'
) - 一个导航守卫 (opens new window)抛出了一个
Error
此时需要检测导航故障。
导航函数返回的promise对象参数为 NavigationFailure 时,说明被导航守卫阻拦,即发生导航故障。并可对NavigationFailure参数调用 isNavigationFailure 判断具体类型(NavigationFailureType)。有三种具体类型
aborted
:在导航守卫中返回false
中断了本次导航。cancelled
: 在当前导航还没有完成之前又有了一个新的导航。比如,在等待导航守卫的过程中又调用了router.push
。duplicated
:导航被阻止,因为我们已经在目标位置了。
const navigationResult = await router.push('/my-profile')
if (navigationResult) {
// 导航被阻止,发生导航故障
// 使用 isNavigationFailure 判断错误类型,注意如果忽略第二个类型错误检察,则只会判断 navigationResult 是否是 NavigationFailureType
if (isNavigationFailure(navigationResult, NavigationFailureType.aborted)) {
// 当为 NavigationFailureType.aborted 时
// 给用户显示一个小通知
showToast('You have unsaved changes, discard and leave anyway?')
}else{
// 错误不是 NavigationFailureType.aborted 类型时
}
} else {
// 导航成功 (包括重新导航的情况)
}
// 检测重定向
await router.push('/my-profile')
if (router.currentRoute.value.redirectedFrom) {
// redirectedFrom 是解析出的路由地址,就像导航守卫中的 to和 from
}
# 动态路由
路由配置一般通过router.js文件进行静态配置,但是VueRouter提供了CLI接口,可以动态的添加或删除路由。主要通过router.addRoute()
和router.removeRoute()
实现。函数参数为路由对象,即同配置内写法一样。
其他:
在导航守卫中添加路由,可以在导航守卫中添加路由,添加完触发重定向即可
删除路由方式:
添加一个名称相同的路由,之前的同名路由会被删除
通过调用 router.addRoute() 返回的回调函数
const removeRoute = router.addRoute(routeRecord) removeRoute() // 删除路由如果存在的话
通过使用 router.removeRoute() 按名称删除路由
router.addRoute({ path: '/about', name: 'about', component: About }) // 删除路由 router.removeRoute('about')
添加嵌套路由,addRoute的第一个参数传入 name
router.addRoute({ name: 'admin', path: '/admin', component: Admin }) // 为 admin 添加嵌套路由 router.addRoute('admin', { path: 'settings', component: AdminSettings }) // 等效于 router.addRoute({ name: 'admin', path: '/admin', component: Admin, children: [{ path: 'settings', component: AdminSettings }], })
查看现有路由:
router.hasRoute()
,检查路由是否存在router.getRoutes()
,获取一个包含所有路由记录的数组。