官方文档:
vue2 对应的路由: vue-router@3 (opens new window)
vue3 对应的路由: vue-router@4 (opens new window)
# 一、基础变化
# 1、路由引入
由于 vue3 采用的是 composition api,所以引入 vue-router 有些许不同:
- vue-router@4
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
const routes = []
export default createRouter({
history: createWebHashHistory() / createWebHistory(),
routes
})
- vue-router@3
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = []
export default new VueRouterr({
mode: 'hash' / 'history'
routes
})
# 2、组件中使用
因为 setup 中不能访 this,所以提供两个 api 来获取 router 和 route , useRouter() 和 useRoute()
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
// router.push()
</script>
而 vue2 中直接使用 this.$router.push()
即可。
<script>
export default {
mounted() {
// this.$router.push()
}
}
</script>
# 3、路由守卫
在 vue-router@3 中,官方强调了要确保 next 函数在任何给定的导航守卫中都被严格调用一次,但实际开发中,还是容易发生意外:
错误示例
- 错误
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
// 如果用户未能验证身份,则 `next` 会被调用两次
next()
})
- 正确
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
在 vue-router@4 中,官方采用直接 return 的方式解决该问题:
vue-router@3 中
- 方式 1
// 设置全局前置守卫
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.path.startWith('/main') && !token) {
next('login')
}
})
export default router
- 方式 2
// 白名单页面
const whiteList = ['/login', '/reg']
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
if (whiteList.includes(to.path)) {
next()
} else {
const token = localStorage.getItem('token')
if (token === null || token === '') {
next('/login')
} else {
next()
}
}
})
- 方式 1
如果后台页面路径都以
/main
开头的换,导航守卫就简单的多了:
// 设置全局前置守卫
router.beforeEach((to, from) => {
const token = localStorage.getItem('token')
if (to.path.startsWith('/main') && !token) {
return '/login'
}
})
export default router
- 方式 2
但也有后台页面路径不规则的情况
// 白名单页面
const whiteList = ['/login', '/reg']
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from) => {
if (whiteList.includes(to.path)) {
return to.path // 不返回或返回undefined也可以
} else {
const token = localStorage.getItem('token')
if (token === null || token === '') {
return '/login' // 也可以返回一个详细的路由对象
} else {
return to.path // 也可以不返回或返回undefined
}
}
})
# 4、Api 变化
在 Vue 3 中,router 和 route 对象只能在组件的 setup 函数、全局上下文中中使用。
组件的 setup 函数中
- 选项式中
import { ref, onMounted } from 'vue'
export default {
setup(props, context) {
const router = context.root.$router
const route = context.root.$route
// 在这里可以使用 router 和 route 对象进行操作
onMounted(() => {
console.log('当前路由路径:', route.path)
})
return {
// 返回给模板使用的数据和方法
}
}
}
- 组合式中
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// 在这里可以使用 router 和 route 对象进行操作
console.log('当前路由路径:', route.path)
return {
// 返回给模板使用的数据和方法
}
全局上下文中
一旦注入完成,你就可以在任何组件或其他地方直接访问 $router
和 $route
,而无需在每个组件的 setup 函数中重新获取。
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
// 将 $router 和 $route 注入全局上下文
app.config.globalProperties.$router = router
app.config.globalProperties.$route = router.currentRoute
app.use(router)
app.mount('#app')
<template>
<div>
<p>当前路由路径: {{ $route.path }}</p>
<router-link to="/other">跳转到其他页面</router-link>
</div>
</template>
<script>
export default {
mounted() {
// 在组件的生命周期钩子中可以直接访问全局上下文中的 $router 和 $route
console.log('全局上下文中的 $router:', this.$router)
console.log('全局上下文中的 $route:', this.$route)
}
}
</script>
// vue2
this.$route
this.$router
// vue3
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
// 示例:
// route.path
// router.currentRoute.value.path (不建议)
// router.push('/login')
# 二、动态权限路由
根据不同的角色,生成不同的菜单
- 方案一:对应的菜单的路由还是注册的,只不过是更加条件显示
- 方案二:根据不同的角色动态的进行路由注册
# 1、添加 addRoute (opens new window)
addRoute(route): () => void
# 这里强调需要name👀属性
addRoute(parentName, route): () => void
const router = createRouter({
routes: [
{
name: 'partOne',
path: '/part1',
component: () => import('../page/PartOne.vue'),
// 二级路由
children: [
{
// 注意二级路由是✨不用加'/'的
name: 'one'
path: 'one',
component: () => import('../page/One.vue')
},
{
name: 'two'
path: 'two',
component: () => import('../page/Two.vue')
}
]
}
]
})
let isAdmin = true // 从服务器中获取角色信息
if (isAdmin) {
// 添加一级路由:addRoute(route): () => void
router.addRoute({
path: '/admin',
component: () => import('../page/Admin.vue')
})
// 添加二级路由:addRoute(parentName, route): () => void
router.addRoute('partOne', {
path: 'two',
component: () => import('../page/Two.vue')
})
}
# 2、删除 removeRoute (opens new window)
提示
利用路由中 name 属性值的唯一性(覆盖、删除)
当路由被删除时,所有的别名和子路由也会被同时删除
vue-router@3 中路由的 ✍ 清除
vue-element-admin 中动态清除已注册的路由方案
在 github 的 vue-router 的官方 issues 中有人提出了解决方案 (opens new window)
利用的是源码的实现:vue-router 会将 routes 选项中的路由数据传送给 createMatcher()方法,该方法会最终返回两个函数:addRoutes()、match()等,这些方法和对象最终会被保存到 router.matcher (opens new window)上
const createRouter = () =>
new Router({
routes: constantRoutes
})
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
export default router
但是上面的这种方案也有一定的问题 (opens new window)
所以项目中的含有通配符的路由()不应该放在 constantRoutes 内!!!
router.addRoutes([...])
// or
router.options.routes = [...]
// 1、覆盖
router.addRoute('partOne', {
name: 'two' // 覆盖
path: 'two-pro',
component: 'TwoPro'
})
// 2、指定名称
router.removeRoute('two') // 删除
// 3、回调
const removeRoute = router.addRoute(routeRecord)
removeRoute() // 删除路由如果存在的话
# 3、其他
- router.hasRoute()
- router.getRoutes()