vue3中的路由
文章目录
一、路由的创建
安装路由后,在项目下创建router目录,并创建index.ts(名字随意)并开始配置路由信息,以下是最简单的一个路由配置文件,配置好路由后,在main.ts中引入并注册路由信息。
import {
createRouter, // 路由hook,用来创建路由实例
type RouteRecordRaw, // 路由类型,直接导出vue-router中定义的路由类型来约自定义的路由类型
createWebHashHistory, // 路由模式,下边有单独讲解
} from "vue-router";
// 直接导入组件,一般像登录页、错误页直接导入,并且配置固定路由信息,以便在项目启动之后就展示
import ParentComponent from "../passvalue/ParentComponent.vue";
// 定义路由信息是一个数组
const routes: Array<RouteRecordRaw> = [
{
path: "/", // 路由访问路劲
name: "Parent", // 路由名称
component: ParentComponent, // 路由组件,具体的组件,根据path或者name跳转的具体组件。
children: [], // 子路由数组,如果该路由下有子路由的话
},
{
//path: '/tab/:foo/:too',
path: "/tab",
name: "Tab",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("../dynamiccomponent/TabComponent.vue"), // 动态导入具体的组件,相当于是懒加载
},
];
// 调用路由hook,用来创建路由实例
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL), // 路由模式
routes: routes, // 路由信息
});
// 暴漏路由实例,在全局中使用
export default router;
二、路由模式
1、createWebHashHistory:vue3中的hash模式,在url中的表现为后边会拼接一个#。
hash模式下,hash部分指的是#以及#后边的那一部分,在页面进行导航时,改变hash部分不会引起页面刷新。一般通过hashchange事件监听hash的改变,改变hash部分的方式有以下几种方式:
- 通过浏览器上的前进后退改变URL。
// 当点击浏览器上的前进后退时,就会触发hashchange事件,并将前后的路由信息回传到回调函数中
window.addEventListener('hashchange', (e) => console.log(e))
- 通过
<a>
改变URL。 - 通过window.location改变URL。
// 直接通过改变location.hash进行路由跳转
location.hash = '/cc'
2、createWebHistory:vue3中的history模式,基于H5的history模式实现
history模式时基于H5的history模式实现的,改变URL有以下几种方式:
- 通过浏览器上的前进后退改变URL。
// 当点击浏览器上的前进后退时,就会触发popstate事件,并将前后的路由信息回传到回调函数中
window.addEventListener('popstate', (e) => console.log(e))
- 通过history.pushState()改变URL。
// 第一个参数是一个对象,可以存一些参数信息,第二个参数是title,第三个参数是要跳转的路由
history.pushState({
},'','/xxx');
三、路由的跳转方式
1、使用<router-link to="/">
根据path跳转或者<router-link :to="Parent">
根据name跳转。
<router-link /*replace*/ to="/"> // 加上replace就会销毁历史记录,浏览器中就不能前进后退
<div style="width: 20x; background-color: red; height: 30px"></div>
</router-link>
<router-view></router-view>
2、使用<a href="/">Parent</a>
跳转。
3、通过button点击事件进行跳转,这个多应用于类似于登录页面这样的场景下,点击按钮之后,登录验证通过之后跳转到首页。
import router from '@/router'
const toPage = (url: string) => {
// router.push(url) // 根据path跳转,这里的url是path
// 使用对象方式跳转的话,可以附带传递参数
// router.replace({ // 使用replace就会销毁历史记录,浏览器中就不能前进后退
// name: url // 根据name跳转,这里的url是name
// })
router.push({
name: url // 根据name跳转,这里的url是name
})
}
const next = () => {
router.go(1) // 使用router.go(1)进行自定义跳转,1代表前进一页
}
const prve = () => {
// router.go(-1) //效果和router.back()是一样的,代表后退一页
router.back()
}
<button @click="toPage('/')" style="width: 200px; background-color: red; height: 30px"></button>
<button @click="next" style="width: 200px; background-color: red; height: 30px">下一页</button>
<button @click="prve" style="width: 200px; background-color: red; height: 30px">上一页</button>
四、路由传参:params/query
路由传参两种方式:
1、name + params
方式:
// 路由定义
{
path: '/tab/:foo/:too', // 新版的vue-router要传递的参数必须在path中以该形式定义才可以获取到值,否则获取不到值
name: 'Tab',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../dynamiccomponent/TabComponent.vue')
}
// 路由跳转
<button @click="toPage('Tab')" style="width: 200px; background-color: red; height: 30px">Tab</button>
import {
useRouter } from 'vue-router'
const router = useRouter()
const toPage = (url: string) => {
router.push({
path: url,
params: {
foo: "foo",
too: "too",
},
});
};
// 接收的时候,也要用params去接收
import {
useRoute } from 'vue-router'
const route = useRoute()
<div>{
{
route.params.foo }}</div>
<div>{
{
route.params.too }}</div>
2、path + query
方式:
// 路由定义
{
path: '/tab',
name: 'Tab',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../dynamiccomponent/TabComponent.vue')
}
// 路由跳转
<button @click="toPage('/tab')" style="width: 200px; background-color: red; height: 30px">Tab</button>
import {
useRouter } from 'vue-router'
const router = useRouter()
const toPage = (url: string) => {
router.push({
path: url,
query: {
foo: "foo",
too: "too",
},
});
};
// 接收的时候,也要用query去接收
import {
useRoute } from 'vue-router'
const route = useRoute()
<div>{
{
route.query.foo }}</div>
<div>{
{
route.query.too }}</div>
五、嵌套路由:children
嵌套路由就是指,父路由下还有子路由,子路由下还有子路由,理论下,可以无限制嵌套子路由,子路由一般会展示在父路由下的<router-view></router-view>
中,在跳转子路由的时候,path中必须是父路由+子路由path
才可以跳转,即必须添加父路由前缀,相当于必须告诉vue具体的子路由路劲才可以找到对应的子路由组件。
{
path: "/", // 路由访问路劲
name: "Parent", // 路由名称
component: ParentComponent, // 路由组件,具体的组件,根据path或者name跳转的具体组件。
children: [
{
path: "", // 路由访问路劲,父组件中如果想默认展示该组件,path为空,即可做到
name: "Parent", // 路由名称
component: ParentComponent, // 路由组件,具体的组件,根据path或者name跳转的具体组件。
children: [
{
path: "/", // 路由访问路劲
name: "Parent", // 路由名称
component: ParentComponent, // 路由组件,具体的组件,根据path或者name跳转的具体组件。
children: [], // 子路由数组,如果该路由下有子路由的话
},
], // 子路由数组,如果该路由下有子路由的话
},
{
path: "/", // 路由访问路劲
name: "Parent", // 路由名称
component: ParentComponent, // 路由组件,具体的组件,根据path或者name跳转的具体组件。
children: [
], // 子路由数组,如果该路由下有子路由的话
},
], // 子路由数组,如果该路由下有子路由的话
},
六、命名视图:components
命名视图指的是一个路由下可以有多个别名的路由组件信息,比如我们有一个tab页签,通过登录人的不同,再该页面下展示不同的信息,可以考虑使用该方式的组件。
{
path: "/", // 路由访问路劲
name: "Parent", // 路由名称
component: ParentComponent
children: [
path: "/", // 路由访问路劲
name: "Parent", // 路由名称
components: {
// 路由组件,具体的组件,根据path或者name跳转的具体组件,如果有多个就用components,单个就用component。
default: () => import('../dynamiccomponent/TabComponent.vue'), // 默认路由组件,跳转到Parent组件之后,这样写<router-view></router-view>,就会展示default命名的组件
Tab11: () => import('../dynamiccomponent/TabComponent.vue'), // Tab11命名的路由组件,跳转到Parent组件之后,这样写<router-view name="Tab11"></router-view>,就会展示Tab11命名的组件
Tab12: () => import('../dynamiccomponent/TabComponent.vue'), // Tab12命名的路由组件,跳转到Parent组件之后,这样写<router-view name="Tab12"></router-view>,就会展示Tab12命名的组件
}
], // 子路由数组,如果该路由下有子路由的话
},
七、路由重定向&别名:redirect & alias
路由重定向指的是假设跳转到某一个路由的时候,想将该跳转重定向到另外一个组件信息。
{
path: "/", // 路由访问路劲
alias: ["/P1", "/P2", "/P3"], // 给Parent路由起多个别名,实际上访问"/P1", "/P2", "/P3"都指向的是Parent所对应的路由组件信息
name: "Parent", // 路由名称
// 当跳转/的时候,就会被重定向到/tab上,访问/tab下的组件信息,以下几种redirect的方式都是等价的
// redirect: "/tab", // 直接使用字符串的方式重定向
// redirect: { // 直接使用对象的的方式重定向,name或者path
// // name: 'Tab1',
// path: '/tab'
// },
redirect(to) {
// 直接使用对象的的方式重定向,该方式是一个接收一个回调函数,通过回调函数返回重定向路由信息,参数to实际上就是父路由的全部信息
console.log('to', to);
// return '/tab' // 回调函数返回字符串重定向路由信息
return {
// 回调函数返回对象重定向路由信息,对象中可以传递参数给子路由,子路由可以接收该参数
// path: '/tab',
// query: {
// n: to.name,
// }
name: 'Tab',
params:{
name: 'zs'}
}
},
component: ParentComponent
children: [
path: "/tab", // 路由访问路劲
name: "Tab", // 路由名称
components: {
// 路由组件,具体的组件,根据path或者name跳转的具体组件,如果有多个就用components,单个就用component。
default: () => import('../dynamiccomponent/TabComponent.vue'), // 默认路由组件,跳转到Parent组件之后,这样写<router-view></router-view>,就会展示default命名的组件
Tab11: () => import('../dynamiccomponent/TabComponent.vue'), // Tab11命名的路由组件,跳转到Parent组件之后,这样写<router-view name="Tab11"></router-view>,就会展示Tab11命名的组件
Tab12: () => import('../dynamiccomponent/TabComponent.vue'), // Tab12命名的路由组件,跳转到Parent组件之后,这样写<router-view name="Tab12"></router-view>,就会展示Tab12命名的组件
}
], // 子路由数组,如果该路由下有子路由的话
},
八、路由守卫:beforeEach & afterEach
main.ts中引入路由之后,添加路由守卫逻辑之后,所有的路由跳转前都会执行beforeEach的逻辑,所有的路由跳转完成之后都会执行afterEach的逻辑。
1、前置守卫:beforeEach
当路由跳转前都会走这个方法,一般应用于权限控制场景。
/**
* 前置守卫: 当路由跳转前都会走这个方法,一般应用于权限控制场景。
* to:要跳转到哪个路由
* from:从哪个路由跳转过来的
* next:允许跳转,是一个函数,如果不执行next()函数,那么路由就不会跳转,相当于是一个放行信号
*/
router.beforeEach((to, from, next) => {
if (to.path == "/") {
// 如果是跳转的登录页,直接放行
next();
return;
}
if (window.sessionStorage.getItem("orkasgb_satoken")) {
// 这里可以做权限判断,比如:token如果存在的时候才允许跳转到首页
if (!user) {
// 如果用户不存在,就去初始化用户信息
// 初始化用户信息
}
next();
return;
}
next("/?redirect" + to.path); // 其他异常情况下,直接重定向到登录页面
});
2、后置守卫:afterEach
当路由跳转完成之后都会走这个方法
/**
* 后置守卫: 当路由跳转完成之后都会走这个方法
* to:要跳转到哪个路由
* from:从哪个路由跳转过来的
*/
router.afterEach((to, from) => {
console.log("from: ", from);
console.log("to: ", to);
});
3、案例:利用前置守卫和后置守卫实现一个全局的组件加载进度条功能
// ProcessBarComponent.vue组件封装
<script setup lang="ts">
import {
ref } from 'vue'
let speed = ref(1)
const bar = ref<HTMLElement>() // 获取进度条dom
const timer = ref<number>(0)
// 开始处理进度条
const startProcess = () => {
const dom = bar.value as HTMLElement
// 实际上类似于setTimeOut定时器
timer.value = window.requestAnimationFrame(function handleBar() {
if (speed.value < 90) {
speed.value += 1
dom.style.width = speed.value + '%' // 设置进度条
timer.value = window.requestAnimationFrame(handleBar) // 递归调用
} else {
speed.value = 1
window.cancelAnimationFrame(timer.value) // 清除定时器
}
})
}
// 结束进度条
const stopProcess = () => {
const dom = bar.value as HTMLElement
speed.value = 100
dom.style.width = speed.value + '%'
}
// 暴漏方法
defineExpose({
startProcess,
stopProcess
})
</script>
<template>
<div class="wraps">
<div ref="bar" class="bar"></div>
</div>
</template>
<style scoped lang="css">
.wraps {
position: fixed;
top: 0px;
width: 100%;
height: 2px;
}
.bar {
height: inherit;
width: 0px;
background-color: blue;
}
</style>
// main.ts中将进度条注册为全局组件,左右的组件加载的时候都会有进度条加载效果
import {
createApp, createVNode, render } from 'vue'
import ProcessBarComponent from './processbar/ProcessBarComponent.vue' // 导入进度条注册
const processBar = createVNode(ProcessBarComponent) // createVNode将导入进度条注册转化为VNode,为了直接将其挂载到body上,并且转化为VNode之后,才能在mian.ts中使用其暴漏出来的方法
render(processBar, document.body) // render函数将VNode挂载到body上
router.beforeEach((to, from, next) => {
processBar.component?.exposed?.startProcess(); // 前置路由守卫调用startProcess开始跑进度条
next();
});
router.afterEach((to, from) => {
processBar.component?.exposed?.stopProcess(); // 后置路由守卫调用stopProcess处理进度条
});
九、路由元信息:
{
path: "/", // 路由访问路劲
name: "Parent", // 路由名称
component: ParentComponent
children: [
path: "/", // 路由访问路劲
name: "Parent", // 路由名称
components: {
// 路由组件,具体的组件,根据path或者name跳转的具体组件,如果有多个就用components,单个就用component。
default: () => import('../dynamiccomponent/TabComponent.vue'), // 默认路由组件,跳转到Parent组件之后,这样写<router-view></router-view>,就会展示default命名的组件
Tab11: () => import('../dynamiccomponent/TabComponent.vue'), // Tab11命名的路由组件,跳转到Parent组件之后,这样写<router-view name="Tab11"></router-view>,就会展示Tab11命名的组件
Tab12: () => import('../dynamiccomponent/TabComponent.vue'), // Tab12命名的路由组件,跳转到Parent组件之后,这样写<router-view name="Tab12"></router-view>,就会展示Tab12命名的组件
}
], // 子路由数组,如果该路由下有子路由的话
meta: {
title: 'Parent页', // 可以设置组件的title,比如在beforeEach中可以通过`document.title = to.meta.title as string`设置组件的名称
keepAlive: true // 可以设置是否缓存该组件信息
}
},
十、路由的滚动行为:scrollBehavior
vue-router提供了一个记录路由行为的函数,用于记录组件的滚动位置,即:当从一个组件返回到另外一个组件的时候,会自动滚动到之前这个组件滚动的位置。
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
/**
* 路由滚动行为: 当从一个组件返回到另外一个组件的时候,会自动滚动到之前这个组件滚动的位置。
* to:要跳转到哪个路由
* from:从哪个路由跳转过来的
* savePosition:记录路由的位置信息
*/
scrollBehavior: (to, from, savePosition) => {
if (savePosition) {
// return savePosition // 可以直接返回一个字符串
return new Promise((res) => {
// 也可以异步返回Promise
setTimeout(() => {
res({
top: 300,
});
}, 500); // 500毫秒之后滚动到300像素的位置
});
} else {
return {
// 也可以返回一个对象,top实际上就是滚动的位置距离组件top的位置,router3好像是x,y
top: 0,
};
}
},
routes: routes,
});
十一、动态路由
动态路由实际就是在登陆验证之后由后台根据用户权限动态返回路由信息,再由前端经过转化处理,形成可以实际访问的路由。详细内容和原理请参考本人另外一篇博客:VUE3浅析—动态路由