文章目录
必备-13.vue-router
什么是vue-router
- vue-router:Vue的路由
- 网址:
https://router.vuejs.org/zh/guide/
多页面/单页面
前端路由有哪些?
- 前端路由(vue-router/react-router-dom)
- 实现SPA(single page application):单页面应用
- 一个项目只有一个页面,我们基于路由,控制页面展示不同的代码片段(或组件),当展示内容改变的时候,页面并不会刷新
- MPA(multi page application):多页面应用
- 一个项目有很多页面,我们做的是页面之间的跳转【每一次跳转都是打开新的页面(相当于页面刷新了)】
单页面应用和多页面应用的项目?
- 单页面应用的项目:
- 移动端大部分都是(追求原生App的操作体验)
- PC端管理系统。。。
- 多页面应用的项目:
- PC端非管理系统的产品
基于JS动态绑定的内容(vue框架都是这样处理)在页面的源代码中是看不到内容的,不利于SEO搜索引擎优化!想做SEO优化,需要服务器渲染(前后端不分离、现在主流的服务器渲染SSR:node+vue(next.js)+react(next.js))
- 现在流行的模式:首屏服务器渲染[骨架屏],其余屏幕还是交给客户端渲染
前端路由模式
-
哈希路由(hash router):监听URL地址后面的哈希值
- 样式:
http://www.xxx/com/index.html#home
- 原理:改变url的哈希值不会刷新页面,通过指定的哈希值使用
window.onhashchange
事件来控制更改页面渲染的内容
- 样式:
-
浏览器路由(browser/history router):基于H5中的historyAPI实现的
- 样式:
http://www.xxx.com/home
- 缺点:需要服务器的支持:(当手动刷新的时候,这个页面不存在,不要返回404,依然返回主页面内容)
- 原理:通过
history.pushState()
方法改变网址,页面不会刷新,再通过window.onpopstate
事件监听网址的改变
- 样式:
-
哈希路由和浏览器路由的区别:
- hash路由地址不好看,browser路由地址好看
- 实现机制不同:hash路由每次跳转,修改的是URL的hash值(页面不刷新);金婷hashchange事件,获取最新的hash值,去路由规则表中,找到对应的组件拿过来渲染!!
- browser路每次跳转,修改的是url地址(页面不刷新
history.pushState
);监听popstate
事件,根据最新的地址找到对应的组件渲染!!·
- browser路每次跳转,修改的是url地址(页面不刷新
- browser路由跳转的地址并不真实存在,所以页面手动刷新,会出现404,此时需要服务器的配合[后台:如果访问的地址不存在,返回主页内容]
- vue脚手架基于
webpack-dev-server
启动的服务(开发环境)、已经完成了"服务器应该对history模式的支持"的相关操作;但是打包部署到服务器上(生产环境),没有webpack-dev-server
,需要服务器的支持(有指定的页面)
vue-router使用
-
第一步:安装:
$npm i vue-router
-
第二步:建立路由规则表:创建路由存放的路径以及index.js 和routes.js文件:
-
//创建index.js文件并配置vue-router的模式和路由规则表 //src/router/index.js :作用是创建vue路由实例,并配置路由 import Vue from "vue"; import VueRouter from "vue-router" import routes from './routes' Vue.use(VueRouter) let router=new VueRouter({ //设置路由模式hash、history(默认) mode:"hash", // 设置路由的匹配规则(路由表):从routes.js中导入 routes }); export default router;
-
//src/router/routes.js :作用是单独管理项目的路由表 import Home from '@/pages/Home.vue' import Analyse from '@/pages/Analyse.vue' import HomeRoutes from "@/router/homeRoutes" const routes=[{ name:"home",//是通过params或路径传参时寻找路由的依据 path:"/",//HASH值(pathname值),也是通过query传参的寻找路由的依据 component:Home//渲染的组件 redirect: "home/custom";//指定重定向的路由path路径 children: xxx;//设置二级路由,我们一般会把它再单独拆出来,就像routes文件一样 meta:{ //路由的携带的元信息 xxx:xxx } },{ path:"/analyse", component:Analyse, // 二级路由:如果感觉嵌套太乱,可以将二级路由单独拆为一个文件 children:HomeRoutes },{ path:'*',//以上规则都不匹配 redirect:'/'//也可以重定向到首页(也可以渲染一个404组件) }] export default routes
-
-
第三步:将router实例添加为全局Vue实例的私有属性
-
//main.js import Vue from 'vue'; import App from '@/App.vue'; import router from '@/router/index'; let aaa=new Vue({ //router会添加成为aaa实例的私有属性 router,//=>this.$router && this.$route render: h => h(App), }).$mount('#app'); console.log(aaa);
-
-
第四步:实现路由跳转(改变url地址或者哈希值)
-
<template> <div id="app"> <nav class="nav-box"> <!-- router-link 内置组件(路由跳转/路由切换) +点击实现路由跳转,基于“to”属性指定跳转地址 +页面渲染的时候,会把router-link渲染为a标签 +页面刷新或路由切换,都会拿最新的地址(或哈希值)和每一个router-link中to的值(或者path属性值)进行匹配;完全“精准”匹配的会给A标签设置:router-link-exact-active router-link-acive 两个样式名;非精准匹配的只设置:router-link-active这个样式类,一点都没匹配的,啥样式都不设置!!=>我们后期可以基于这个特点,给当前匹配的导航设置选中特殊样式 精准匹配/非精准匹配/完全不匹配 页面地址:/order to的地址: / 非精准匹配 (包含一个完整的) / : 任何地址都包含一个完整的斜杠 /home2 VS /home 不算 /home/list VS /home 算 /analyse 完全不匹配 /order 精准匹配(一毛一样) --> <router-link to="/">首页</router-link> <router-link to="/analyse">分析页</router-link> <router-link :to="{ path: '/order' }">订单页</router-link> </nav> <main class="main-box"> <!-- router-view 内置组件(路由容器) 用来渲染“基于路由规则匹配组件的” : 当页面刷新或者路由跳转后,vue都会拿url最新的地址(或者hash值)去路由表中进行匹配,把匹配到的组件,放到router-view容器中渲染 而每一次的路由切换,都是把上一个渲染的组件释放(或销毁 beforeDestroy destroyed),把新匹配的组件进行渲染(beforeCreate->created->beforeMount->mounted)--> <router-view></router-view> </main> </div> </template>
-
-
第五步:设置一个容器,可以在指定的位置,把基于路由规则匹配的组件进行渲染
- 以上的
<router-view></router-view>
就是我们指定的组件渲染的位置
- 以上的
router-link
-
router-link 内置组件(路由跳转/路由切换)
-
实现页面跳转:点击实现路由跳转,基于“to”属性指定跳转地址
-
<router-link to="/">首页</router-link> <router-link to="/analyse">分析页</router-link> <router-link :to="{ path: '/order' }">订单页</router-link>
-
-
页面渲染的时候,会把router-link渲染为a标签
-
页面刷新或路由切换:都会拿最新的地址(或哈希值)与每一个
router-link
中to属性的值(或者path属性值)进行匹配;完全“精准”匹配的会给A标签设置:router-link-exact-active router-link-acive
两个样式名;非精准匹配的只设置:router-link-active
这个样式类,一点都没匹配的,啥样式都不设置!!=>我们后期可以基于这个特点,给当前匹配的导航设置选中特殊样式
精准匹配/非精准匹配/完全不匹配:
页面地址:/order
to的地址:
/ 非精准匹配 (包含一个完整的)
/ : 任何地址都包含一个完整的斜杠
/home2 VS /home 不算
/home/list VS /home 算(前面router-link的值的包含后面路由表里的值)
/analyse 完全不匹配
/order 精准匹配(一毛一样)
router-view
-
router-view 内置组件(路由容器):用来渲染“基于路由规则匹配组件的” :
- 当页面刷新或者路由跳转后,vue都会拿url最新的地址(或者hash值)去路由表中进行匹配,把匹配到的组件,放到
router-view
容器中渲染 - 而每一次的路由切换,都是把上一个渲染的组件释放(或销毁
beforeDestroy=>destroyed
),把新匹配的组件进行渲染(beforeCreate->created->beforeMount->mounted)
- 当页面刷新或者路由跳转后,vue都会拿url最新的地址(或者hash值)去路由表中进行匹配,把匹配到的组件,放到
-
用法:
<main class="main-box"> <router-view></router-view> </main>
二级路由
-
路由会对路由表中祖先级和父级,进行非精准匹配,对最低层级做精准匹配
-
二级路由可以直接在一级路由表中配置,但这样会显得代码嵌套层数太多,我们可以将二级路由抽出来,放在一个js文件中,再进行导入引用:
import Home from '@/pages/Home.vue'
import HomeRoutes from "@/router/homeRoutes"
// 路由根据路由表时自上而下匹配的,只要有一个匹配就直接跳转,所以path:"*"不能在最前面匹配
const routes=[{
path:"/",//HASH值(pathname值)
redirect:"/home"//重定向,直接跳转到/home
},{
path:"/home",
component:Home,
// 二级路由:如果感觉嵌套太乱,可以将二级路由单独拆为一个文件
children:HomeRoutes//这里是我们导入的二级路由
},]
-
二级路由homeRoutes.js文件的写法:
-
二级路由也是路由表的形式,只是里面的路径:
- 如果不以斜杠开头,则表示拼接到父级路由后面
- 如果以斜杠开头,则表示井号#后面的全部路径,不会拼接父路由路径
-
// 管理Home板块的路由表 const homeRoutes=[ { path:"",//如果/home后面没有跟路径,则默认将它重定向到'/home/customerlist',也就是让他第一次打开home就在custmerlist页面 redirect:'/home/customerlist' }, { path:"customerlist",//不是斜杠开头,就会拼在一级目录前:=>/home/customerlist component:()=>import("@/pages/home/CustomerList.vue") }, { path:"/home/customeradd",//是斜杠开头,就认为这个路径是绝地路径:=>/home/customeradd component:()=>import("@/pages/home/CustomerAdd.vue") }, { path:"visitList", component:()=>import("@/pages/home/VisitList.vue") } ]; export default homeRoutes;
-
路由懒加载
-
vue性能优化方案之一:路由懒加载
-
问题:如果在编写路由表的时候,事先导入了所有组件,根据规则渲染不同的组件,这样最后build打包的时候,会把所有组件全部打包到一个js中,js文件较大,页面第一次渲染请求js时间过长,延长了白屏事件~~~
-
解决:路由懒加载依托于ES6中提供的import函数
- 1、webpack打包的时候实现代码切割:在路由表中已经导入的组件(一般只导入默认需要展示的,就是第一次访问页面需要展示的),打包到一个js中,其余的组件根据情况,分割为一个或多个js文件!
- 2、最开加载页面,只把主JS文件导入,当路由匹配了某个规则,需要渲染某个组件,再把这个js文件加载
-
-
**写法:**在路由表中各项的component属性后面用ES6函数:
()=>import (/* webpackChunkName: 'analyse'
的形式导入指定路径组件的就是==路由懒加载==-
const routes=[{ path:"/",//HASH值(pathname值) redirect:"/home"//重定向,直接跳转到/home },{ path:"/home", //这是不做路由懒加载的 component:Home, children:HomeRoutes },{ path:"/analyse", //这是做路由懒加载的 component:()=>import (/* webpackChunkName: 'analyse' */"@/pages/Analyse.vue"), }]
-
按组件打包js,按需导入
- 问题:我们每通过
()=>import("@/pages/home/VisitList.vue")
的形式使组件路由懒加载,都会形成一个单独的js文件,这样就会导致项目中js文件很多。 - 解决:
component:()=>import (/* webpackChunkName: 'analyse' */"@/pages/Analyse.vue"),
- 在import内,最前面加上一句
/* webpackChunkName: 'analyse'*/
这样,它以及它子孙组件就会打包成一个js组件,实现了按包分割的功效,减少了js文件数量
- 在import内,最前面加上一句
VueRouter实例
r o u t e 与 route与 route与router的属性和方法
-
当我们"new Vue({router})",会给每一个组件(vue实例)注入两个私有属性
this.$route
:存储路由匹配的信息this.$router:
VueRouter的实例,可以调用VueRouter.prototype上的方法实现路由跳转
-
$route:接收方,存储当前路由的详细信息
fullPath
:完整的路由地址(含问号传参信息)path
;不含问号传参信息query:{}
以键值对的方式存储问号传参信息params:{}
存储的是“路径传参信息”或者"隐式传参"信息name
:路由名字meta:
:获取当前路由元信息matched
:记录hash
:存储除hash
路由内容外,单独设置的哈希值:#home?xxx#aaa
,hash的值就是#aaa
-
$router:执行方,执行
-
私有属性方法:
mode
路由的模式
-
公有属性方法:
-
push
:跳转到指定路由:this.$router.push("/analyse")
等同于<router-link to="analyse">
-
可以写成对象,这样写可以传参:
-
this.$router.push({ //发送query的方式 path:'/analyse', query:{}, /// name, params:{}, })
-
-
replace
:也是跳转到指定路由,区别在于:push是新增一条历史记录,而replace是修改本条历史记录 -
go(n)
:以当前历史记录为标准,前进或后退,基于n指定步数,go(-1)后退一步,go(1)前进一步 -
back
:->go(-1) -
forward
:->go(1)
-
addRoute
-
addRoutes
:动态向路由表中加入新的规则记录(动态路由):可以防止客户端恶意访问 -
getRoutes
:获取现有的路由表
-
-
路由切换的传参方式
-
路由切换(或者跳转)的传参方式:每一次路由切换,想把一些信息传递给下一个组件,A组件跳转到B组件
-
办法一->问号传参:
#/xxx?xxx=xxx
[丑&可以在地址栏中看到传递信息]-
//A组件 this.$router.push({ path:"/B", query:{ lx:1, from:"weixin" } }) //B组件 //基于this.$route.query获取即可->{lx:1,from:"weixin"} ------------ //因为地址栏中有,即使手动刷新B野蛮,信息还可以获取
-
-
办法二->隐式传参:地址栏中看不见,从内部把信息传递过去&&需要基于路由名字跳转,基于path跳转不可以
-
path是路由地址,name是路由名字,隐式传参需要基于name跳转
-
命名路由:后期实现路由跳转,可以不基于path,基于name也可以跳转
//A组件 this.$router.push({ name:"B"//用name指定要跳转到那个路由 path:"/B", params:{ lx:1, from:"weixin" } }) //B组件 //基于this.$route.params获取即可->{lx:1,from:"weixin"} ------------ //因为地址栏中有,即使手动刷新B野蛮,信息还可以获取
- 特点:因为传递的信息在地址栏中不存在,所以在B页面刷新,传递的信息都没有了~~
-
-
方法三->路径参数:把需要传递的数据,当做路由地址的一部分
#/xxx/100
[常用]因为不丑-
**第一步:**设置路由表
{//这种是不传参数时匹配的customadd路由路径 name:"home_customadd", path: 'customadd', component: () => import(/* webpackChunkName: "home" */'@/pages/home/CustomAdd.vue') }, {//这种是需要传参数id和name匹配的路由路径 name:"home_customadd", path: 'customadd/:id/:name', component: () => import(/* webpackChunkName: "home" */'@/pages/home/CustomAdd.vue') }
-
第二步:实现路由跳转
this.$router.push("/home/customadd/100/sjh");
-
第三步:获取
this.$router.params()->{id:"100",name:"sjh"};
-
特殊情况:路由切换的时候,要释放的上一个组件和即将渲染的下一个组件,是同一个组件,此时这个组件即不会被释放,也不会被重新渲染(也就是第一次渲染的逻辑就不会触发了,例如:created就不会再执行了)
-
解决方案一:基于watch监听路由的变化监听路由的变化,从而做一些事情
watch:{ $route:{//监控vm实例中的私有$route属性 handler(){ ... }, immediate:true,//重新渲染组件(第一次渲染逻辑),让监听器的处理也执行 } }
-
解决方案二:把需要依赖路由变化而变化的信息,都设置为计算属性
computed:{ title(){ let {id}=this,$route.params; return id?"修改客户":"新增客户"; } }
-
-
-
VueRouter实例的属性
VueRouter的实例私有属性就是创建路由时可以添加的属性:
let router=new VueRouter({
mode:"push",
routes:[{
name:"sjh",
path:"/home"
...
}]
})
routes中各属性
-
name:"名字"
:命名路由,设置路由表时必须加的属性,名字不能重复,后期实现路由跳转可以不基于path,基于name也可以跳转 -
path:"/home"
:表示在路由表中注册/home
路径的路由信息,当请求的hash值或地址为/home
时,就会去路由表中精准匹配到/home
路径下指定的组件,并进行切换 -
component:[组件]
:指定需要切换的组件 -
redirect:"/list"
:将这个路径重定向到path为/list
的路径下面 -
children:[数组]
:二级路由,也如同组件,是数组形式,一般二级路由会单独提出来,再导入 -
meta:{任何类型}
:路由元信息:记录每一个路由表的一些基础信息[自定义的]this.$router.meta
:获取
路由的导航守卫
-
导航的最重要用途是做身份验证和登录校验,我们一般会在
beforeEach
前置守卫当中做登录态的校验【只要不是进入登录页,都要在这里校验一下用户是否登陆,如果登录了,正常进行后面的步骤,如果没登录,直接让其返回登录页!】 -
在每一次页面刷新(或者路由跳转),路由进行规则匹配的时候,也会触发一些钩子函数,而这些钩子函数,称为导航守卫函数
-
完整的导航解析流程:A组件->B组件
-
1、
beforeRouterLeave
:释放A组件,触发A组件的beforeRouterLeave
钩子函数- A组件释放,即将开启B组件的路由解析和渲染
-
2、
beforeEach
:(前置守卫)【写在创建路由处】触发全局前置守卫函数beforeEach
router.beforeEach((to,from,next)=>{ //to:去哪 //from:从哪来 //next:进行下一步,如果next不执行,卡在这里就结束了,后面啥也不做 //next():之前规划咋走,则继续往下走 //next('/login'):直接走到/login这个路由上 let title=to.meta.title; if(title) document.title=title; next(); })
-
3、
beforeRouterUpdate
:如果A和B是相同组件,则会触发组件的beforeRouterUpdate
钩子函数 -
4、
beforeEnter
:(独享守卫)【写在路由表中】再触发匹配的路由表中的独享守卫函数- B组件被激活,开始进入到B组件中进行渲染
-
5、
beforeRouterEnter
:触发B组件的beforeRouterEnter
钩子函数 -
6、
beforeResolve
😗*(全局解析守卫)**触发全局解析守卫函数`` -
7、
afterEach
:**(全局后置守卫)**触发全局后置守卫函数
-
真实项目中,我们最常用的是全局前置守卫beforeEach
:因为每一次页面刷新(或者路由跳转)、也不论是从哪跳到哪,beforeEach
一定会被触发执行
实战中
- 场景切换动画
keep-alive
:组件缓存scrollBehavior(to,from,savePosition){return {x:0,y:0}}
:滚动行为,组件跳转的时候规定组件显示 滚动的位置
复习
什么是vue-router?
//vue-路由是实现单页面应用中各组件之间相互切换的一种方式,通过路由表来控制组件之间的切换
单页面与多页面?
// 单页面:我们的移动端APP和web端管理系统,一般都是单页面,因为想通过路由给用户一种原生移动APP的感觉。
//缺点:不能做SEO优化
//多页面:非管理系统PC端一般都做多页面,是整个页面与页面之间的跳转
// 多页面与单页的区别?
//单页面是组件间的切换,无需刷新页面,无需跳转链接,可以实现切换动画效果,能通过vuex实现组件间的数据共享并同步,开发难度较低,用户体验更好
vue-router的用法?
//1/安装vue-router插件,创建router目录,里面创建index.js和routes.js
//2/index.js中存放vueRouter实例,实例中可以对vueRouter进行配置,比如说mode:模式,默认是history我们也经常会改成hash,还有routes:配置路由表,是一个数组,我们一般把这个路由表提出来,放到routes.js文件,再导入使用。
//3/每一个routes都是一个数组,数组中的每一项都是一个对象,每个对象的属性包含但不限于:
//name:命名路由 path:请求路径 meta:{}携带信息 component:组件 redirect:重定向方向 children:二级路由
//router-link和router-view的用法
//router-link :to:"地址";作用是控制路由间的切换,:to是切换的路由路径,而router-view则是显示切换的路由组件视图
路由懒加载:
//问题:如果我们不做路由懒加载,就会将所有的组件组合成一个js文件,这样就会导致加载文件时过慢,所以需要做路由懒加载
//实现路由懒加载:component:()=>{import("@/pages/aaa.vue")};//不是直接导入组件,而是通过import指定路径来延迟导入,这就是路由蓝加载
//实现组合打包:因为我们的组件做了路由懒加载之后,每个文件又都会拆分成一个独立的js文件,这样就导致请求次数过多,所以我们需要将有关联的组件,组合打包
// component:import(/*webpackChunkName:"Home"*/ "@/pages/Home.js"),这样就会将Home及它下面的组件打包成一个js文件,实现了组合打包
路由之间的传参:
//问好传参:通过?xxx=xxx的形式传参,
this.$router.push({
path:"/home",
query:{
name:"sjh"
}
})
// this.$route.query 接收
// 隐式传参:通过params传参
this.$router.push({
name:"home",
params:{
name:"sjh"
}
})
// this.$route.params接收
// 路径传参:通过home/:id/:name的形式在路由表中定义path
this.$roter.push({
path:"/home/100/sjh"
})
// this.$route.path 接收
$route和$router的属性
// $route主要作为当前路由个体,存放当前路由的详细信息,有:
//fullPath/path/name/meta/query/params/matched/hash
//$router更像是路由表的管理员,它最大的作用就是控制路由的跳转
//mode/push()/replace()/go(-1)/back()/forward()/addRoute()/addRoutes()【实现动态路由】/getRoutes()
路由的导航守卫:
// beforeRouterLeave:第一步,销毁旧路由之前
// beforeEach:(全局前置守卫)第二步,在跳转到任何路由之前做的事,最常用[写在new VueRouter的实例上]
// beforeRouterUpdate:第三步:如果两次切换的组件相同,会触发这个事件
// beforeEnter(独享守卫)第四步:写在单个路由中,表示当调用到这个路由时触发的事件函数
// beforeRouterEnter():第五步:当组件开始渲染时触发的函数
// beforeResolve():全局解析守卫
// afterEach():全局前置守卫
我们如何修改选中路由的样式
//当我们切换一个路由时,路由表会根据我们切换的path进行查询,查询的结果分三种
//精准匹配/不完全匹配/不匹配
//精准匹配:请求的路径与路由表中的path完全相同
// 不完全匹配:请求的path包含了路由表中path的一部分,比如请求的是/home/age,路由表中的"/"和"/home"都是非标准匹配
//不匹配:完全不一样的叫不匹配
//精准匹配的组件:会在其标签上加一个class:router-link-exact-active
//不完全匹配的组件:会在其标签上加一个class:router-link-active
//我们基于这个两个类名,就可以修改组件样式了
前端工程化
- 模块化😦js模块化,css模块化,资源模块化)
- 组件化😦复用现有的ui,结构、样式、行为)
- 规范化😦模块结构的划分,编码规范化,接口规范化,文档规范化,git分支管理)
- 自动化😦自动化构建,自动部署自动化测试)