Vue3——Router4教程(小满版本)(一)https://developer.aliyun.com/article/1470378
第六章——命名视图
default:默认的意思,这个具体是什么意思呢?就是说你默认显示出来的就是这个default
,就可以具体显示你想要的页面(你可以定义多个view也就是视图),跳转的时候同时显示多个视图的内容
命名视图可以在同一级(同一个组件)中展示更多的路由视图,而不是嵌套显示。 命名视图可以让一个组件中具有多个路由渲染出口,这对于一些特定的布局组件非常有用。 命名视图的概念非常类似于 “具名插槽”,并且视图的默认名称也是 default
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' const routes: Array<RouteRecordRaw> = [ { path: "/", components: {//注意这里发生了变化,多了一个s default: () => import('../components/layout/menu.vue'),//转到默认的 header: () => import('../components/layout/header.vue'),//router-view里面的name填header,下面content同理,这个一旦填写了,我们在template中可以定义多个<router-view>视图,然后跳转的时候同时显示多个视图的内容 content: () => import('../components/layout/content.vue'), } }, ] const router = createRouter({ history: createWebHistory(), routes }) export default router
对应 Router-view 通过 name 对应组件
<div> <router-view></router-view> <router-view name="header"></router-view> <router-view name="content"></router-view> </div>
- 注意事项
- 这个是components而不是component,因为通过这个多加一个s的,我们可以一次性跳转到多个路由视图中,需要在template中去额外定义多的路由视图,并且通过你在components中取的名字来进行绑定,比如在上面代码块中,我们使用了header跟content这两个名字,那在使用的时候就需要name绑定这两个名字
第七章——重定向-别名
重定向 - redirect
字符串形式配置,访问 / 重定向到 /user (地址栏显示 /, 内容为 /user 路由的内容)
他的属性名就是redirect:
三次书写形式
- 字符串的写法:
const routes: Array<RouteRecordRaw> = [ { path:'/', component:()=> import('../components/root.vue'), redirect:'/user1',//写法上的区别在这里,这redirect是字符串的写法 children:[ { path:'/user1', components:{ default:()=> import('../components/A.vue') } }, { path:'/user2', components:{ bbb:()=> import('../components/B.vue'), ccc:()=> import('../components/C.vue') } } ] } ]
- 对象的写法:
const routes: Array<RouteRecordRaw> = [ { path: '/', component: () => import('../components/root.vue'), redirect: { path: '/user1' },//这里是对象形式的写法,path也会自动对应到下方children的path(子路由)中 children: [ { path: '/user1', components: { default: () => import('../components/A.vue') } }, { path: '/user2', components: { bbb: () => import('../components/B.vue'), ccc: () => import('../components/C.vue') } } ] } ]
- 函数模式(可以传参)=> 回调形式
const routes: Array<RouteRecordRaw> = [ { path: '/', component: () => import('../components/root.vue'), redirect: (to) => {//写法是以to的形式,把to括起来的这个括号可加可不加 return {//必须要返回值,也就是return path: '/user1', query: to.query//由于这里to没有传值过来,我们可以暂时自己写一个 //query:{name:"小余"},这个name=小余会在URL,也就是路径中体现出来 } }, children: [ { path: '/user1', components: { default: () => import('../components/A.vue') } }, { path: '/user2', components: { bbb: () => import('../components/B.vue'), ccc: () => import('../components/C.vue') } } ] } ]
别名 alias
将 /
别名为 /
root,意味着当用户访问 /
root 时,URL 仍然是 /user
,但会被匹配为用户正在访问 /
通俗的说就是给路由取多个名字,但访问的组件都是同一个(比如我们都知道鲁迅跟周树人,这是两个名字,但其实都是指同一个人)
const routes: Array<RouteRecordRaw> = [ { path: '/',//我就是下面说的path路径 component: () => import('../components/root.vue'),//访问/root,/root2,/root3都是展示这个组件 alias:["/root","/root2","/root3"],//三个名字,或者说是上面这个path路径的外号或者说是别名 children: [ { path: 'user1', components: { default: () => import('../components/A.vue') } }, { path: 'user2', components: { bbb: () => import('../components/B.vue'), ccc: () => import('../components/C.vue') } } ] } ]
第八章——导航守卫-前置守卫
全局前置守卫
router.beforeEach
小满称呼:中间件
//所有跳转、后退都会走这个函数 router.beforeEach((to, form, next) => { console.log(to, form); next() })
小满在这里做的是一个登录的校验,使用了组件库这里都不进行说明
小满在这里使用了一个@别名,在这里对这个进行一个解释
在vite.config.ts文件下,代码如下 export default defineConfig({ plugins:[vue(),vueJsx()], resolve:{ alias:{ '@':fileURLToPath(new URL('./src',import.meta.url)), } } })
- 这样的作用是对你的文件起了一个别名,此处的@的别名对应的是./src。从上面的路径中我们也可以看到
resolve.alias
在组件之间相互引用时,可能是下面这样的:
import Hello from '../src.components/Hello';
其中的路径是相对于当前页面的。 但是如果嵌套等更为复杂,那么写起来会比较麻烦。但是如果我们通过这样的配置:
resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@pages': path.join(__dirname, "..", "src", "pages"), "@components": path.join(__dirname, "..", "src", "components"), // 注意: 静态资源通过src,不能这么设置。 // "@assets": path.join(__dirname, "..", "src", "assets"), }
其中 vue$ 表示引入 vue,就可以像下面这么写:
import Vue from 'vue'
另外,对于 @pages 和 @components 我们就可以直接引用了,而省去了一大堆的复杂应用,另外通过 @可以消除歧义。如下所示:
import Hello from '@components/Hello'; import App from '@pages/App'
**值得注意的时: 在 webpack.config.js 中我们不能使用../ 以及./ 这种形式的路径方式,而是通过 path.join 和 __dirname 这种形式来表示路径,否则会报错。**
另外: 在组件中,我们会引用一些静态文件,即 static 下的文件, 这时我们就不能用 alias 下的配置了,而必须使用一般的配置方式。
我没找到vite的,根据webpack触类旁通
const rules = reactive({ user:[ { required:true,//证明是不是必填 message:"失败给提示信息", type:"string",//类型 tiggle:"change"//触发的条件 } ] })
每个守卫方法接收三个参数:
to: Route, 即将要进入的目标 路由对象; from: Route,当前导航正要离开的路由; next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。 next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。 next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
案例 权限判断
const whileList = ['/']//白名单 router.beforeEach((to, from, next) => { let token = localStorage.getItem('token') //白名单 有值 或者登陆过存储了token信息可以跳转 否则就去登录页面 if (whileList.includes(to.path) || token) { next()//next()是放行的意思 } else { next({ path:'/'//回到登录界面 }) } })
呃,这个使用组件库的过程不好写笔记,建议自己看看视频
全局后置守卫(内容较深,后续回来补笔记)
使用场景一般可以用来做 loadingBar
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
和前置路由守卫相比为什么没有next功能呢?那是因为前置路由守卫是看大门前面的,而后置路由守卫则是在大门的后面。前面的保安如果都放行进来了,那后面的保安还要再确认一遍就太过繁复也没有必要啦
router.afterEach((to,from)=>{ Vnode.component?.exposed?.endLoading() })
loadingBar 组件
<template> <div class="wraps"> <div ref="bar" class="bar"></div> </div> </template> <script setup lang='ts'> import { ref, onMounted } from 'vue' let speed = ref<number>(1)//这里是通过TS的规范类型为数字number类型,以下类似 let bar = ref<HTMLElement>() let timer = ref<number>(0)//默认为0 const startLoading = () => {//开始进度条 let dom = bar.value as HTMLElement;//因为有可能为undefined,所以我们使用as进行一个断言为HTMLElement speed.value = 1//初始化speed的值 timer.value = window.requestAnimationFrame(function fn() {//requestAnimationFrame会将回流重绘收集起来只走一次,性能会更好。这里可以使用箭头函数,但我们选择使用function。回调函数只走一次,所以我们起了一个fn的名字拿去递归 if (speed.value < 90) { speed.value += 1; dom.style.width = speed.value + '%' timer.value = window.requestAnimationFrame(fn)//递归 } else { speed.value = 1; window.cancelAnimationFrame(timer.value) } }) } const endLoading = () => {//结束进度条 let dom = bar.value as HTMLElement; setTimeout(() => { window.requestAnimationFrame(() => {//箭头函数的写法 speed.value = 100;//其实显示的就是进度条的进度,结束了就是100% dom.style.width = speed.value + '%' }) }, 500) } defineExpose({//defineExpose 可以将方法主动暴露出来,通过对象的形式。然后能够在父组件中进行接收 //当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样 { startLoading: number, endLoading: number } (ref 会和在普通实例中一样被自动解包) startLoading, endLoading }) </script> <style scoped lang="less">//样式 .wraps { position: fixed; top: 0; width: 100%;//这些都是决定进度条的样式的 height: 2px; .bar { height: inherit; width: 0; background: blue; } } </style>
main.ts
import loadingBar from './components/loadingBar.vue' const Vnode = createVNode(loadingBar)//createVNode是Vue3中提供的方法,用于创造DOM节点 render(Vnode, document.body) console.log(Vnode); router.beforeEach((to, from, next) => { Vnode.component?.exposed?.startLoading()//问号为可选的意思 }) router.afterEach((to, from) => { Vnode.component?.exposed?.endLoading() })
上述牵扯到的知识点补充:
计时器一直是javascriptz动画的核心技术。而编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样
才能让不同的动画效果显得平滑流畅;另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化
大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms
而setTimeout和setIntervall的问题是,它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器U线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行
requestAnimationFrame.采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果
requestAnimationFrame 比起 setTimeout、setInterval 的优势主要有两点:
1、requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒 60 帧。
2、在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的的 cpu,gpu 和内存使用量。
第九章——路由元信息
路由元信息
通过路由记录的 meta
属性可以定义路由的元信息。使用路由元信息可以在路由中附加自定义的数据,例如:
- 权限校验标识。
- 路由组件的过渡名称。
- 路由组件持久化缓存 (keep-alive) 的相关配置。
- 标题名称
有时我们想将一些信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的 meta
属性来实现,并且它可以在路由地址和导航守卫上都被访问到。定义路由的时候你可以这样配置 meta
字段
我们可以在导航守卫或者是路由对象中访问路由的元信息数据。
const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', component: () => import('@/views/Login.vue'), meta: { title: "登录"//这里定义什么都行,最终这里的数据是会被获取到的 } }, { path: '/index', component: () => import('@/views/Index.vue'), meta: { title: "首页", } } ] })
使用 TS 扩展
如果不使用扩展 将会是 unknow 类型
declare module 'vue-router' { interface RouteMeta {//接口写法,将title变成是否可选的string类型 title?: string//这个问号不写也没有关系,意思就变为title必须为string(字符串)类型 } }
如何获取元信息
在组件中可以通过 this.$route.meta 来获取
在路由中获取:
router.beforeEach((to, from, next) => { //通过to.meta来获取路由元信息 console.log(to.meta.requiresAuth); })
- 通过这类方法我们可以在配置文件中通过router.beforeEach,也就是前置路由守卫中进行设置
- 前置路由守卫有三个参数分别为
to
,from
,next
。我们这里使用到to,意思是将要去哪里的意思,我们通过to.meta.title拿到了我们在routers中对应的路由中配置的mate中的title,然后将这个值赋值给document.title - 产生的效果就是能够在路由跳转后,网页的标题也随之发生改变,变成对应路由的信息(比如我们设定好的登录和首页)
第十章——路由过渡动效
过渡动效
想要在你的路径组件上使用转场,并对导航进行动画处理,你需要使用 v-slot API:
使用组件库的时候记得先安装(npm安装方式:npm install animate.css)后import引入
<router-view #default="{route,Component}">//#default,像这种使用井号的是简写方式 //能解出来两个属性,route为当前路由的信息,Component就是当前的组件 <transition :enter-active-class="`animate__animated ${route.meta.transition}`">//animate__animated是动画组件库的前缀,然后active-class就是高亮样式的选项,动画组件库和路由的结合形成的过度效果 <component :is="Component"></component> </transition> </router-view>
上面的用法会对所有的路由使用相同的过渡。如果你想让每个路由的组件有不同的过渡,你可以将元信息和动态的 name
结合在一起,放在 上:
declare module 'vue-router'{ interface RouteMeta { title:string, transition:string, } } const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', component: () => import('@/views/Login.vue'), meta:{ title:"登录页面", transition:"animate__fadeInUp",//动画组件库 } }, { path: '/index', component: () => import('@/views/Index.vue'), meta:{ title:"首页!!!", transition:"animate__bounceIn", } } ] })
第十一章——滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。vue-router 可以自定义路由切换时页面如何滚动。
当创建一个 Router 实例,你可以提供一个 scrollBehavior
方法
const router = createRouter({ history: createWebHistory(), scrollBehavior: (to, from, savePosition) => {//scrollBehavior滚动的属性,savePosition参数为标记的距离 if(savePosition){ return savePosition }else{ return{ top:0//或者可以不进行判断直接return你想要的数据,就跟随你的心意距离顶部还有多少距离 } } },
const router = createRouter({ history: createWebHistory(), scrollBehavior: (to, from, savePosition) => {//scrollBehavior滚动的属性,savePosition参数为标记的距离 console.log(to, '==============>', savePosition); return new Promise((r) => {//异步 setTimeout(() => { r({ top: 10000//top是Vue3的写法 }) }, 2000); }) },
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进 / 后退 按钮触发) 时才可用。
scrollBehavior 返回滚动位置的对象信息,长这样:
- { left: number, top: number }
const router = createRouter({ history: createWebHistory(), scrollBehavior: (to, from, savePosition) => { return { top: 200//这个就是跟随心意的写法了,不进行if判断,直接根据自己喜好返回距离顶部的距离 } } })
第十二章——动态路由
我们一般使用动态路由都是后台会返回一个路由表前端通过调接口拿到后处理 (后端处理路由)
主要使用的方法就是 router.addRoute
调用接口需要安装axios,命令:npm install axios -S
import引入axios
添加路由
动态路由主要通过两个函数实现。router.addRoute()
和 router.removeRoute()
。它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push()
或 router.replace()
来手动导航,才能显示该新路由
router.addRoute({ path: '/about', component: About })
删除路由
有几个不同的方法来删除现有的路由:
- 通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由:
router.addRoute({ path: '/about', name: 'about', component: About }) // 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的 router.addRoute({ path: '/other', name: 'about', component: Other })
- 通过调用
router.addRoute()
返回的回调:
const removeRoute = router.addRoute(routeRecord) removeRoute() // 删除路由如果存在的话
- 当路由没有名称时,这很有用。
- 通过使用
router.removeRoute()
按名称删除路由:
router.addRoute({ path: '/about', name: 'about', component: About }) // 删除路由 router.removeRoute('about')
当路由被删除时,所有的别名和子路由也会被同时删除
查看现有路由
Vue Router 提供了两个功能来查看现有的路由:
- router.hasRoute():检查路由是否存在。
- router.getRoutes():获取一个包含所有路由记录的数组。
案例
前端代码
注意一个事项 vite 在使用动态路由的时候无法使用别名 @ 必须使用相对路径
const initRouter = async () => { const result = await axios.get('http://localhost:9999/login', { params: formInline });//get是params参数,post没有限制。formInline是表单的信息,通过.user.password之类的能获取到账号密码。 result.data.route.forEach((v: any) => {//后端返回的信息进行动态添加 router.addRoute({ path: v.path, name: v.name, //这儿不能使用@ component: () => import(`../views/${v.component}`)//最关键的组件需要我们动态的去拼接,这里使用模板字符串 //然后就是import()动态引入 }) router.push('/index')//跳转页面,验证通过,进入登录页面 }) console.log(router.getRoutes()); }
后端代码 nodejs express
import express, { Express, Request, Response } from 'express' const app: Express = express() app.get('/login', (req: Request, res: Response) => {//这里使用get请求所以必须使用query参数 res.header("Access-Control-Allow-Origin", "*"); if (req.query.user == 'admin' && req.query.password == '123456') {//根据前端传过来的信息返回不同的信息回去 res.json({ route: [ { path: "/demo1", name: "Demo1", component: 'demo1.vue' }, { path: "/demo2", name: "Demo2", component: 'demo2.vue' }, { path: "/demo3", name: "Demo3", component: 'demo3.vue' } ] }) }else{ res.json({ code:400, mesage:"账号密码错误" }) } }) app.listen(9999, () => { console.log('http://localhost:9999'); })