路由:目的就是将组件映射到路由上,路由知道哪里渲染组件,更方便创建单页面引用(更新视图但不需要重新请求页面)
一、vue3.x及更低版本
二、vue4.x(适用于vue3项目中)
(1)安装路由包
(2)在单独的文件中配置路由方便管理及引入全局使用
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
const router = createRouter({
history: createWebHashHistory(),
routes: staticRoutes,
});
export const staticRoutes: Array<any> = [
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: {
title: i18n.global.t('message.staticRoutes.signIn'),
},
},
{
path: '/404',
name: 'notFound',
component: () => import('@/views/error/404.vue'),
meta: {
title: i18n.global.t('message.staticRoutes.notFound'),
},
},
];
// 可以设置全局导航守卫等,并将配置的路由导出
export default router;
(3)引入全局使用(一般在main.js中,在根组件引入)
import router from './router';
import App from './App.vue';
const app = createApp(App);
app.use(router)
知识点梳理
1、基础知识点
(1)router-link (链接路由 类似a标签)
(2)router-view(路由出口,渲染当前url匹配的组件会在router-view处渲染)
(3)使用
全局注册的路由可通过this. r o u t e r 和 t h i s . router和this. router和this.route
在实例中使用4.x中需要手动引入并使用
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
// 添加路由项
router.push({ path: , query: });
router.push({ name: , params: });
// 拿当前路由(url信息)
route.path、route.query(url存在参数)、route.params、route.hash等
(4)动态路由匹配(使用:进行匹配):场景:有时我们对同一个组件User会对所有用户进行渲染,但是用户id不同。当动态路由被匹配时,它的动态路由参数会在this.$route.params上(所以使用时通过params传参进行匹配)
针对这种动态路由匹配相同的组件时,为了提高性能,组件不会销毁再创建,导致组件的生命周期不会调用。
办法:在组件上监听动态参数处理逻辑 $route.params 或者利用组件导航守卫beforeRouteUpdate在组件上等更新前做逻辑
{ path: '/users/:id', component: User },
(5)匹配所有路由或404not found路由
const routes = [
// 将匹配所有内容并将其放在 `$route.params.pathMatch` 下
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
// 将匹配以 `/user-` 开头的所有内容,并将其放在 `$route.params.afterUser` 下
{ path: '/user-:afterUser(.*)', component: UserGeneric },
]
this.$router.push({
name: 'NotFound',
// 保留当前路径并删除第一个字符,以避免目标 URL 以 `//` 开头。
params: { pathMatch: this.$route.path.substring(1).split('/') },
// 保留现有的查询和 hash 值,如果有的话
query: this.$route.query,
hash: this.$route.hash,
})
(6)路由path不区分大小写,且后面可加/,但是可通过路由语法进行设置
(7)嵌套路由:router-view嵌套及配置route时嵌套(嵌套的命名路由:设备name字段)
一种使用场景是:方便管理,将大导航中的路由统一配置在一个地方,只需要一个路由出口,因为path写的是完整的(外层没配置组件,如下)
另一种使用场景是:当外层下有不同的内组件,需要切换时,例如User/profile User/post(在app中有路由出口,在User中有路由出口)
{
component: undefined,
hidden: false,
meta: {
icon: 'el-icon-s-home',
title: 'GIS配置',
breadArr: ['可视化配置', 'GIS配置'],
},
name: 'GISConfig',
path: '/visconfig/GISConfig',
children: [
{
component: 'visconfig/GISConfig/layerConfig/index',
hidden: false,
meta: {
icon: 'el-icon-s-home',
title: '图层配置',
breadArr: ['可视化配置', 'GIS配置', '图层配置'],
},
name: 'layerConfig',
path: '/visconfig/GISConfig/layerConfig',
},
{
component: 'visconfig/GISConfig/labelTypeConfig/index',
hidden: false,
meta: {
icon: 'el-icon-s-home',
title: '标签类型配置',
breadArr: ['可视化配置', 'GIS配置', '标签类型配置'],
},
name: 'labelTypeConfig',
path: '/visconfig/GISConfig/labelTypeConfig',
},
],
},
// 第二种场景案例
<div id="app">
<router-view></router-view> // app根部路由出口
</div>
const User = {
template: 'User {{ $route.params.id }}',
}
const routes = [{ path: '/user/:id', component: User }]
const User = {
template: `
User {{ $route.params.id }}
// User中的路由出口
`,
}
// 配置嵌套的路由(以 / 开头的嵌套路径将被视为根路径)
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 内部
path: 'posts',
component: UserPosts,
},
],
},
]
(8)编程式导航:除了使用router-link语法,可以利用router实例方法,用代码实现
// 路由实例 $router 或者手动引入
// 1、实例方法push(会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL)点击 相当于调用 router.push(...) 注意:提供了 path,params 会被忽略
this.$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' })
// 2、实例方法replace 替换当前路由(同push方法,但是不会添加到history记录中)
// 声明式
<router-link :to="..." replace>
// 编程式
router.replace(...) // 参数同push参数可以是字符串路径,也可以是描述地址的对象
router.push({ path: '/home', replace: true }) // 直接在push中增加replace参数
// 3、实例方法go(横跨历史,在历史堆栈中前进或后退多少步)同window.history.go(n)
// 向前移动一条记录,与 router.forward() 相同
router.go(1)
// 返回一条记录,与 router.back() 相同
router.go(-1)
// 前进 3 条记录
router.go(3)
// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)
router.push、router.replace 和 router.go 是 window.history.pushState、window.history.replaceState 和 window.history.go 的翻版,它们确实模仿了 window.history 的 API
(9)命名路由 配置路由时增加name字段,这样路由的相关操作不用书写path路径
(10)命名视图(同级展示多个视图而不是嵌套展示时使用)与嵌套命名视图(不常用,组合使用即可)
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view> // 不设默认default
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
(11)重定向和别名
// 1、重定向是通过路由配置routes增加redirect实现的(导航守卫不会对这种home有效果,只添加在目标路由上)。用户访问 /home 时,URL 会被 / 替换,然后匹配成 /,渲染对应的组件
const routes = [{ path: '/home', redirect: '/' }] // home 重定向到 / (可以是path)
const routes = [{ path: '/home', redirect: { name: 'homepage' } }] // (可以是配置)
const routes = [
{
// /search/screens -> /search?q=screens
path: '/search/:searchText',
redirect: to => { // 可以是函数返回重定向路由 的path或对象配置
// 方法接收目标路由作为参数
// return 重定向的字符串路径/路径对象
return { path: '/search', query: { q: to.params.searchText } }
},
},
]
// 2、相对重定向
const routes = [
{
// 将总是把/users/123/posts重定向到/users/123/profile。
path: '/users/:id/posts',
redirect: to => {
// 该函数接收目标路由作为参数
// 相对位置不以`/`开头
// 或 { path: 'profile'}
return 'profile'
},
},
]
// 3、别名:另一个名字。将 / 别名为 /home,当用户访问 /home 时,URL 仍然是 /home,但会被匹配为用户正在访问 /所对应组件
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
{ path: '', component: UserList, alias: ['/people', 'list'] }, // 可以定义多个别名
(12)路由组件传参
// 将props传递给路由组件:前面讲到动态匹配路由,会最终暴露在params上,但这种方式只能用于特定的url /home:id
// 可以替换成props形式,确保任何组件可用(直接传参数也行,不必非得是动态路由匹配的)。
const User = {
// 请确保添加一个与路由参数完全相同的 prop 名
props: ['id'],
template: 'User {{ id }}'
}
const routes = [{ path: '/user/:id', component: User, props: true }]
// 布尔模式 props:true,route.params 将被设置为组件的 props
// 对象模式 当 props 是一个对象时,它将原样设置为组件 props(对于静态的props时适合用)
const routes = [
{
path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false } // 组件中的props也是此对象
}
]
// 函数模式
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q }) // 可实现静态值与路由值相结合
}
]
(13) 不同的历史模式:在进行路由实例常见时history配置允许我们在不同的历史模式中进行选择(html5和hash两种)
// 1、hash模式 http://10.233.54.161/preview.html#/device/type
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
onst router = createRouter({
history: createWebHashHistory(),
routes: staticRoutes,
});
// html5模式
const router = createRouter({
history: createWebHistory(),
routes: [
//...
],
})
总结:两种实现原理
hash模式:#及后面的部分(同window.location.hash获取到的部分)原理是利用onhashchange 事件,可通过 window 对象监听
(hash 虽然包含在浏览器的url中,但是它不会包括在HTTP请求中,所以改变hash不会重新加载页面;每一次改变hash,都会在浏览器的访问历史中增加一个记录,所以可以实现前端路由“更新视图但不重新请求页面”的功能,同时也可使用浏览器的 前进、后退 功能)
history 模式(需要浏览器支持):利用html5提供的history API来实现url的变化,history.pushState()和history.repalceState()这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。使用 pushState 事件来监听 url 的变化,从而对页面进行跳转(渲染)
(使用 history 模式时,需要配合后端的支持,因为url路径修改后,会请求服务器的数据,如果服务器中没有相应的响应或者资源,则会出现404错误;所以需要在服务器上配置一个默认路径,当url匹配不到任何静态资源的时候,返回到同一个页面(可配置成首页 index.html ))
2、进阶
(1)导航守卫:通过跳转、取消的方式守卫导航(全局的,单个路由独享的,或者组件级的)
// 全局导航守卫(用于所有的路由)
// 1、 全局前置守卫 router.beforeEach
router.beforeEach((to, from,next) => {
// 书写校验逻辑,校验完成执行next(),或者跳转至指定的登录页等.如果遇到了意料之外的情况,可能会抛出一个 Error。这会取消导航并且调用 router.onError() 注册过的回调
// 返回 false 以取消导航
return false
})
// 2、全局解析守卫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
}
}
}
})
// 3、全局后置守卫 router.afterEach(它不会再改变导航本身了,因为已经导航跳转了)它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
// 路由独享的守卫(给需要的路由设置,适用于每个路由)
// 进入路由时触发 beforeEnter,它只在改变导航时触发,改变参数等不会触发
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
// 组件内的守卫
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`
},
}
(2)路由元信息:任意信息附加在路由上
{
path: '/noPermission',
name: 'noPermission',
component: () => import('@/views/error/noPermission.vue'),
meta: {
title: '无权限',
auth: ['admin', 'test'],
},
},
// 访问:
一个路由匹配到的所有路由记录会暴露为 $route 对象的$route.matched 数组。我们需要遍历这个数组来检查路由记录中的 meta 字段,但是 Vue Router 还为你提供了一个 $route.meta 方法
(3)数据获取(进入路由后,需要在服务器获取数据)
方式1:导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
方式2;导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航(beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法)
(4)组合式API
组合式api中模板中可直接使用 r o u t e r 及 router及 router及route但在setup中需要手动引入
在setup中使用一样但是名字变了 onBeforeRouteLeave onBeforeRouteUpdate
(5)过渡动效
// 所有路由进行过渡
<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>
(6)滚动行为(路由切换时如何滚动页面,保持在原位置还是顶部等)在支持 history.pushState 的浏览器中可用
const router = createRouter({
history: createWebHashHistory(),
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
// 始终滚动到顶部
// return { top: 0 }
// 始终在元素 #main 上方滚动 10px
return {
// el: document.getElementById('main'),
el: '#main',
top: -10,
}
}
})
// 还可以设置滚动到瞄点或者延迟滚动
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth',
}
}
}
})
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ left: 0, top: 0 })
}, 500)
})
},
})
(7)路由懒加载:利用import导入组件,实现路由懒加载
完整的导航解析流程
(1)导航被触发 (声明式或编程式触发)
(2)在失活的组件里调用 beforeRouteLeave 守卫(组件上的路由守卫)
(3)调用全局的 beforeEach 守卫 (全局前置守卫)
(4)在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
(5)在路由配置里调用 beforeEnter (路由独享的守卫)
(6)解析异步路由组件。
(7)在被激活的组件里调用 beforeRouteEnter。
(8)调用全局的 beforeResolve 守卫(2.5+) (组件内的守卫和异步完成后及确认前)
(9)导航被确认
(10)调用全局的 afterEach 钩子。
(11)触发 DOM 更新。
(12)调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入(最后是导航被确认后到组件上的进入)
vue-router 3.x到vue-router4.x的变化
(1)new Router 变成 createRouter
(2)history 配置取代 mode
(3)删除了*通配符路由