前言
为了适配vue3,路由已经更新的v4版本了。与之前有不小的区别,因此打算按照官方文档,重新梳理学习一下,顺便巩固一下知识。
以下内容基于官方文档
安装
npm install vue-router@4
基础
入门
创建一个router目录
创建一个router目录;目录下创建一个src目录,用来放模块的路由;再创建一个index.ts
用来导出所有路由
index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
// 练习模块
import test from "./src/test";
const history = createWebHistory();
const routes: Array<RouteRecordRaw> = [
{
//重定向,找不到路由时跳转到home
path: "/:pathMatch(.*)*",
redirect: "/home",
},
{
path: "/",
redirect: "/home",
},
{
path: "/home",
name: "home",
component: () => import("../views/home.vue"),
},
{
...test,
},
];
const router = createRouter({
history,
routes,
});
export default router;
createWebHistory
也可以用createWebHashHistory
替换,查了一下两者区别如下:
- createWebHistory路由模式路径不带#号(生产环境下不能直接访问项目,需要nginx转发)
- createWebHashHistory路由模式路径带#号
main.ts
import router from "./router/index";
app.use(router)
router-link 和 router-view
router-link
<router-link to="/">Go to Home</router-link>
创建链接,to
用来指向要跳转的路由;不过一般用编程式导航
router-view
<router-view></router-view>
路由匹配到的组件将渲染在这里。如果存在子路由,那么在父路由对应的vue文件中也要添加router-view
动态路由匹配
路径参数
const routes = [
// 动态字段以冒号开始
{ path: '/users/:id', component: User },
]
现在像 /users/johnny 和 /users/jolyne 这样的 URL 都会映射到同一个路由。通过this.$route.params
可以获取到这个变量的值。
相同组件实例被复用时,组件的生命周期钩子不会被调用,应该通过监听$route.params
来进行响应。
捕获所有路由或404路由
const routes = [
// 将匹配所有内容并将其放在 `$route.params.pathMatch` 下
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
// 将匹配以 `/user-` 开头的所有内容,并将其放在 `$route.params.afterUser` 下
{ path: '/user-:afterUser(.*)', component: UserGeneric },
]
这个也与之前不同,记得以前就一个*
嵌套路由
const routes = [
{
path: '/user/:id',
component: User,
children: [
// 当 /user/:id 匹配成功
// UserHome 将被渲染到 User 的 <router-view> 内部
{ path: '', component: UserHome },
// ...其他子路由
],
},
]
嵌套路由需要注意的就是在父级路由对应的vue文件里不要忘记router-view
,否则子路由对应的内容不会显示。
编程式导航
相对于通过router-link
实现导航,编程式导航的应用更加灵活。
通过router.push()
来实现路由的跳转
// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。
比较常用的还是通过path
的形式,实现导航。
replace
作用类似于router.push()
,但是导航时不会往history中添加新记录。
go
该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步
// 向前移动一条记录,与 router.forward() 相同
router.go(1)
// 返回一条记录,与router.back() 相同
router.go(-1)
命名路由
命名路由就是给路由起一个名字,在编程式导航中可以通过name进行路由跳转
重定向
重定向也是通过 routes 配置来完成,下面例子是从 /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 } }
},
}
]
进阶
导航守卫
正如其名,vue-router 提供的导航守卫主要用来 通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。
全局前置路由
使用router.beforeEach
注册一个全局前置守卫:
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
每个守卫方法接收两个参数:
- to:即将要进入的目标路由
- from:当前导航正要离开的路由
可以返回的值如下:
- false:取消当前的导航
- 一个路由地址:通过一个路由地址跳转到一个不同的地址
示例:如果路径是 /test
就让他直接跳转到/test/button
router.beforeEach((to, from) => {
// console.log(to);
if (to.path == "/test") {
return "/test/button";
}
});
除了返回一个路径字符串,其实也可以返回一个对象,可以使用路由的其他参数
if (to.path == "/test") {
return {
path: "/test/button",
query: {
id: 5,
},
};
}
可选的第三个参数:next
在之前的 Vue Router 版本中,也是可以使用 第三个参数 next 的。它仍然是被支持的,但是官方好像不推荐。容易出错。
全局解析守卫
全局解析路由router.beforeResolve
跟全局前置守卫路由类似。但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。
官方说法的大体意思就是。路由跳转之前会解析一些数据,在这个过程里你可以执行一些其他操作。
全局后置钩子
全局后置钩子:afterEach
不会改变导航本身,但是对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
router.afterEach((to, from) => {
console.log(to, from);
document.title = "哈哈哈";
});
路由独享守卫
在配置路由时,定义beforeEnter
守卫:
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。
你也可以将一个函数数组传递给 beforeEnter,这在为不同的路由重用守卫时很有用:
function removeQueryParams(to) {
if (Object.keys(to.query).length)
return { path: to.path, query: {}, hash: to.hash }
}
function removeHash(to) {
if (to.hash) return { path: to.path, query: to.query, hash: '' }
}
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: [removeQueryParams, removeHash],
},
{
path: '/about',
component: UserDetails,
beforeEnter: [removeQueryParams],
},
]
组件内的守卫
你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)
可以为路由组件添加以下配置
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
如果你正在使用组合 API 和 setup 函数来编写组件,你可以通过 onBeforeRouteUpdate 和 onBeforeRouteLeave 分别添加 update 和 leave 守卫。
示例:
setup() {
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// 取消导航并停留在同一页面上
if (!answer) return false
})
}
路由元信息
官方文档是这样介绍的:
有时,你可能希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。定义路由的时候你可以这样配置 meta 字段:
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true }
},
{
path: ':id',
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false }
}
]
}
]
router.beforeEach((to, from) => {
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: '/login',
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
}
}
})
TypeScript
可以通过扩展 RouteMeta 接口来规范输入 meta 字段的格式:
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
// 是可选的
isAdmin?: boolean
// 每个路由都必须声明
requiresAuth: boolean
}
}
组合式API
在 setup 中访问路由和当前路由
在 setup 里面没有访问 this,所以我们不能再直接访问 this. r o u t e r 或 t h i s . router 或 this. router或this.route。作为替代,我们使用 useRouter 函数:
import { useRouter, useRoute } from 'vue-router'
setup() {
const router = useRouter()
const route = useRoute()
onMounted(() => {
console.log(router, route)
})
}
router用来执行一些路由方法,例如:back()
、go()
、push()
等方法
route可以用来获取一些属性,例如:query
、params
、path
等属性
导航式守卫
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
setup() {
// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// 取消导航并停留在同一页面上
if (!answer) return false
})
const userData = ref()
// 与 beforeRouteUpdate 相同,无法访问 `this`
onBeforeRouteUpdate(async (to, from) => {
//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
过渡动画
全局动画
<router-view v-slot="{ Component }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>
单个路由的过渡
如果你想让每个路由的组件有不同的过渡,你可以将元信息和动态的 name 结合在一起,放在 上:
const routes = [
{
path: '/custom-transition',
component: PanelLeft,
meta: { transition: 'slide-left' },
},
{
path: '/other-transition',
component: PanelRight,
meta: { transition: 'slide-right' },
},
]
<router-view v-slot="{ Component, route }">
<!-- 使用任何自定义过渡和回退到 `fade` -->
<transition :name="route.meta.transition || 'fade'">
<component :is="Component" />
</transition>
</router-view>
基于路由的动态过渡
官方提供的这个例子就很有意思
<!-- 使用动态过渡名称 -->
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition">
<component :is="Component" />
</transition>
</router-view>
我们可以添加一个 全局后置钩子,根据路径的深度动态添加信息到 meta 字段。
router.afterEach((to, from) => {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
to.meta.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
})
滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState 的浏览器中可用。
当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:
const router = createRouter({
history: createWebHashHistory(),
routes: [...],
scrollBehavior(to, from, savedPosition) {
// 始终滚动到顶部
return { top: 0 }
},
})
返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
},
})
延迟滚动
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ left: 0, top: 0 })
}, 500)
})
},
})
路由懒加载
// 将
// import UserDetails from './views/UserDetails'
// 替换成
const UserDetails = () => import('./views/UserDetails')