前言
主要是了解 vue-router 的基础使用,在接触 vue 项目时,必然会用到路由相关内容,那么就必不可少需要了解他, 本文就介绍一下 vue-router
主要参考自 vue-router参考地址
部分参考自 路由守卫以及部分源码
vue-router
安装
pnpm add vue-router@4
router 介绍
创建项目默认给下面的 createRouter--histroy(createWebHistory 历史记录模式后面介绍),创建某个历史记录模式的,这里是默认是 h5 模式
routes
routers 就是我们的路由了,里面常见的就四个参数,path(路由)、name(路由名称)、component(组件)、children(子路由集合),还有一个 meta(路由元) 暂不介绍,基本也只有一些场景用
path(路由):路由path路径,也就是我们 url 后面的**/*之类的信息,例如:...juejin.../editor/draftscomponent(组件):就是我们自己写的路由组件,我们的ui,可以直接导入,也可以懒加载导入(通过import函数导入,例如:import('../views/Dashboard/Location.vue'))children(子路由集合):嵌套子路由,子路由和路由写法一样,只不过多了一层,一般外面会有一些公共UI用于切换,或者单纯突出层级也会嵌套子路由name(路由名称):自定义的路由名称,可以简化路由 path,避免使用过长 path,或者拼写错误问题,如果每个路由都使用 name,并且统一管理name 并使用 export 的路由名称,那么再也不用担心拼写错误问题了meta(路由元):可以附加一些任意信息在路由上,可以理解为制定路由额外携带的参数,路由守卫、保活或者一些场景可能会用到该参数,例如设置路由保活参数:meta: { keepAlive: true }
下面是我写的一个简易的 demo 案例中的路由,
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', redirect: '/login' }, //redirect重定向 不传递 / 表示相对位置,只替换最后一个路由
{
path: '/login',
name: 'Login', //命名路由,可以通过 name 直接跳转到对应的路由,router.push({ name: 'Login'})
// 懒加载,使用的时候才会加载,也就是提升了加载速度,并不会节省带宽,工程大页面多会有效果,能够提升启动速度
component: () => Login,
},
{ path: '/dashboard', redirect: '/dashboard/location' },
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
children: [
{
path: '/dashboard/location',
name: 'Location',
component: Location,
// component: import('../views/Dashboard/Location.vue')
},
{
path: '/dashboard/menu/:id', //动态路由,这里里面的都是用的一个(实际看自己需要)
name: 'Menu',
component: Menu,
children: [
// { path: '', component: MenuHome }, //如果就希望空节点存在页面,可以path设置为空串
{
path: '/dashboard/menu/subMenu/:id', //动态路由,这里面的都是用的一个(实际看自己需要) :id+表示后面可以匹配1~多个 *表示后面可以匹配0~多个 ?0~1个,就不多说了
name: 'SubMenu',
component: Menu,
},
],
},
],
},
{ path: '/:pathMatch(.*)', component: NotFound404 }, //直接匹配所有路径,但是优先级比较低,适用于匹配不到的情况
],
})
其中 为了演绎嵌套子路由,配合了 Element-Plus 的 menu,Dashboard 长这样哈
在 app 中注册 router 才能使用
import router from './router'
app.use(router)
动态路由匹配
动态路由匹配,也就是我们路由里面可以匹配其他自定义参数内容,可以使用 :参数名 的方式设置匹配一个参数
//这里面的都是用的一个(实际看自己需要)
path: '/dashboard/menu/:id', 后面固定匹配一个id
那么 /dashboard/menu/1234 路由中, 1234 就是params 参数
也可以通过正则表达式过滤参数类型,过滤个数等,需要注意的是,匹配多个的情况,末尾不能再有路由
path: '/dashboard/menu/:id(\\d+)', //仅匹配纯数字
path: '/dashboard/menu/:id+', 匹配1~多个
path: '/dashboard/menu/:id*', 匹配0~多个
path: '/dashboard/menu/:id?', //匹配一个或者0
匹配所有路由
除了标准的匹配,有时候还需要做兜底匹配,路由不正确时,跳转统一到 NotFound 即可
{ path: '/:pathMatch(.*)', component: NotFound404 }, //直接匹配所有路径,但是优先级比较低,适用于匹配不到的情况
ps:有些可能会需要跳转到无授权页面,这个就不需要兜底了,可以使用后面的守卫做
响应路由参数变化(重复使用一个路由组件)
使用带有参数的路由时需要注意的是,当用户从 /users/johnny 导航到 /users/jolyne 时,相同的组件实例将被重复使用,两个路由都渲染同个组件,也意味着组件的生命周期钩子不会被调用
const route = useRoute()
watch(() => route.params.id, (newId, oldId) => {
// 对路由变化做出响应...
})
ps:使用场景可以参考一下掘金的内容页跳转推荐的内容页
router跳转和传参
可以使用 RouterLink 来直接跳转,不想用 RouterLink,也可以使用自己的按钮,直接 UI 模版中的 @click 使用全局 $router 对象直接跳转
<RouterLink to="/dashboard">测试RouterLink登录</RouterLink>
<div class="cursor-pointer text-center" @click="$router.push('/dashboard')">
测试#router登录
</div>
功能略复杂,可能带判断或者带参数的,可以使用 router 对象跳转,一般使用 Vue3 的 script 标签设置 setup,没有 this 那就只能使用 useRouter,如果是怀旧版,可以直接 this.$router
//template
<el-button type="primary" @click="goHome">正常登录走这是</el-button>
//script
<script setup lang="ts">
import { RouterLink, useRouter } from 'vue-router'
const router = useRouter()
const goHome = () => {
//直接 push
// router.push('/dashboard')
//通过name跳转
// router.push({
// name: 'Location'
// })
//跳转页面替换当前页面实现切换路由
router.replace('/dashboard')
//通过 query 参数实现路由传参,不需要动态路由匹配
// router.replace({
// path: '/dashboard',
// query: {
// username: 'shuai',
// },
// })
//通过路由 params 模式传参,这里假设在 user 路由后面传递参数,需要使用动态路由匹配了
//router.push('/dashboard/user/18181883')
}
</script>
获取 params、query参数
可以直接使用 route 来获取 router 中携带的 params、query 参数
//模版中直接使用 $route 获取参数
$route.params.id
$route.query.id
//使用this获取
this.$route.params.id
this.$route.query.id
setup 使用 useRoute() 即可
const route = useRoute()
route.params.id
route.query.id
使用props接收params参数
// 路由配置
const routes = [
{
path: '/user/:id',
component: User,
props: true // 将路由参数作为组件 props 传入
}
]
//使用 props 接收
defineProps({
id: String
})
<template>
<div>用户ID: {{ id }}</div>
</template>
RouterView插槽
RouterView 是 router 路由到视图的入口(插槽),通过 RouterView,我们可以将指定路由对应的组件,直接替换应用到到 RouterView所在位置,所以其是一个动态控制内容显示的一个入口
//加入页面只有一个 <RouterView /> 那么进入到该页面的时候整个页面都显示该路由对应的页面了
<RouterView />
如果被其他视图包裹起来,那么该路由组件显示的就是 RouterView 对应位置,如下所示
<div>
<div /> //左侧
<RouterView />
</div>
还记开始做的 dashboard 默认显示的案例图么, 左边 element-menu 右边 RouterView
<div class="flex">
<el-menu
...
></el-menu>
<RouterView />
</div>
因此,内部仍然可以使用 <RouterView /> 作为二级路由、三级路由组件的入口
重定向 redirect
有时候有些页面没内容,但希望让其显示我们想显示的内容,例如: 根路由没有设置的话,我们希望他跳转到 Login 路由,可以如下设置
//redirect重定向 不传递 / 表示相对位置,只替换最后一个路由
{ path: '/', redirect: '/login' },
导航守卫
可以参考导航守卫
beforeEach(全局前置守卫)
全局前置守卫 beforeEach 是在,路由跳转前触发,可以重定向、取消、继续跳转,我们可以在这里处理我们的验权等逻辑
router.beforeEach(async (to, from, next) => {
if (未登录) {
return { path: 'Login' }
} else if (黑名单) {
//直接取消跳转
return false
} else if (授权没通过) {
return { path: 'NotPermission401' }
}
//继续跳转
next()
})
beforeResolve(全局解析守卫)
你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置
afterEach(全局后置守卫)
afterEach 和 beforeEach 相反,他是在路由跳转完成后触发,参数包括to, from 由于此时路由已经完成跳转 所以不会再有next,因此只能做跳转完成后的操作,例如:用户行为统计等
afterEach(路由独享守卫)
beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects。它们只有在 从一个不同的 路由导航时,才会被触发。
你也可以将一个函数数组传递给 beforeEnter,这在为不同的路由重用守卫时很有用
例如:父路由和子路由都可以使用 beforeEnter。如果放在父级路由上,路由在具有相同父级的子路由之间移动时,它不会被触发
routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from, next) => {
next()
},
},
]
组件路由守卫
有这三个 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave,分别是进入、更新时、离开时
<script>
export default {
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
</script>
beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:
js
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持 传递回调,因为没有必要了:
js
beforeRouteUpdate (to, from) {
// just use `this`
this.name = to.params.name
}
这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消。
js
beforeRouteLeave (to, from) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (!answer) return false
}