简述
vue-router路由管理也是vue中特别重要的一篇,下面来看看在本篇中主要学习哪些内容
- 学习路由管理的前置知识
- vue-router的基本使用
- vue-router的详解
- vue-router嵌套路由
- vue-router参数传递
- vue-router导航守卫
- keep-alive的使用
学习路由管理的前置知识
本节中主要讲网站架构发展过程,以及前后端路由,前后端渲染的概念
最后介绍当更改浏览器URL却不向服务器再申请资源的方法,下面来看具体详情:
网站架构发展过程
- 前后端不分离阶段
以前学习JSP的时候,或者使用thymeleaf的时候,就是前后端不分离的时候,没有用ajax
直接拿到数据在页面中嵌入java代码等,并且也是后端根据用户的请求,将页面响应给用户。 - 前后端分离阶段
再之后,就是学习了ajax,通过Jquery发送ajax请求,从后端获取到数据,再通过JS遍历数据展示
在页面中,但是注意,这个时候依然是后端根据用户请求,选择将某个页面发送给用户。 - 单页面富应用阶段
到现在,整个项目只有一个页面了,其他页面都是一个个前端人员编写的组件通过Vue渲染出来的
富应用说的就是多个组件,在这个阶段中,用户只在最开始进入网站的时候发送请求给后端,接收到
后端数据之后,便把所有静态数据全部渲染了,当用户点击页面某个按钮,其实是跳转到了相应的组件中
这个阶段在前后端分离阶段的基础之上增加了一点,就是页面的跳转由前端主导,而不是后端。
- 前后端不分离阶段
前后端路由和前后端渲染的概念
通过上面的解释,我们很容易知道,后端路由指的是,后端处理URL和页面之间的映射关系,即跳转页面后端来做
前端路由则是跳转页面由前端进行处理,这不,vue-router就是干这事的。
后端渲染指的是,数据在后端就已经渲染到页面中,直接给用户发送HTML页面就完事
前端渲染则是,数据由前端渲染,你后端只是提供数据而已,好处是,以后IOS端,桌面软件都可以共用一个后端即可。更改浏览器URL却不向服务器再申请资源的方法
上面说过,在单页面富应用阶段,只有在用户进入网站的时候才会向服务器申请唯一一套资源,之后用户每次
点击按钮,会发生url改变,但是并不向后端请求数据,而是通过切换组件达到跳转页面的效果
这种改变浏览器url,却不向服务器发出请求是怎么做的呢?下面是两种方式:/* 1,通过改变浏览器的hash值,使得改变浏览器url,但不向服务器发出请求 使用这种方式改变url,被称为hash模式,hash模式也是vue路由中默认使用的模式,hash模式有个特点,就是url路径中会带有# */ /* 2,使用html5的history对象使得改变url,但不请求服务器,使用history对象方式被称为history模式 history的pushState方法,相当于入栈操作,使用这个方法,会将这个url对应的组件放进一个栈结构中, 页面始终显示栈顶url对应的组件页面 history的back方法,相当于出栈操作,就是浏览器返回上一个页面,不过在vue中称为返回上一个组件 history的replaceState方法,这个方法相当于替换了栈顶的url,这个意思是,浏览器不能返回上一个页面 history的go方法,history.back()等价于history.go(-1),history.forward()等价于history.go(1),指定跳转到某个页面 */ /* 2021年10月3日更新: hash模式和history模式,区别不仅仅是浏览器路径中的#,当项目被打包部署到服务器上时 如果刷新页面,hash模式的#后面的路径不会发送到后端服务器中,不会产生问题, 而如果是history模式,则一旦页面刷新,浏览器错把路由跳转的路径当成后端服务器接口请求, 结果服务器没这接口,直接404,项目上线时要想使用history模式,需要后端的配合,进行相应的处理 或者配置nginx */
vue-router的基本使用
目前三大框架都有自己的路由实现,vue就是vue-router,路由用于设定访问路径,将路径和组件映射起来
在vue-router的单页面应用中,页面路径的改变就是组件的切换,下面是vue路由使用步骤:
安装vue-router
npm install vue-router --save //运行时依赖如果没有自动在src目录下创建router目录的话,我们需要在src下创建一个router目录并在其中创建index.js文件
该JS文件是vue-router的配置文件,我们需要在其中导入vue和vue-router,并且将vue-router注册进Vue中
准确来说,vue-router是vue的插件,只要是插件都需要注册到vue中,插件主要是用来为vue添加全局功能的
下面是router/index.js初始配置:import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) //注册插件 const routes = [ //具体的映射关系,配置url对应某些组件的 ] const router = new VueRouter({ //创建router实例,并导出该实例 mode: 'history', //使用history模式更改url,路径中就不会有# base: process.env.BASE_URL, routes //将映射关系装载到router中,es6属性的增强写法,等同于routers:routers }) export default router最后在main.js中,将router装载到vue实例中,下面是main.js中内容
import Vue from 'vue' import App from './App.vue' import router from './router' //当不写后面具体文件时,默认找该路径下的index.js文件 new Vue({ router, //装载路由对象 render: h => h(App) //组件篇讲过,代替template+components,就是生成一个<App/>标签 }).$mount('#app') //绑定页面中id为app的div标签
总结下来,不过是安装插件,注册插件,在路由配置文件index.js中配置路由映射信息,再在main.js中导入router
上面这些是基本配置,下面我们创建一个HelloVueRouter组件,并为其配置路由映射关系
首先在src/components目录下创建一个HelloVueRouter.vue文件,具体内容如下:
//组件中就显示Hello VueRouter <template> Hello VueRouter </template> <script> export default { //导出组件对象 name: "HelloVueRouter" } </script> <style scoped> </style>在router/index.js中配置路由信息
//只需要在这里添加映射信息,其余不变 import helloVueRouter from '../components/HelloVueRouter.vue' const routes = [ { path: '/helloVueRouter', name : 'helloVueRouter', component: helloVueRouter } ]在App.vue中的template中使用vue-router的组件标签,router-link标签和router-view标签,这两个标签
都是vue-router中的组件,其中router-link标签用于跳转组件,router-view标签用于将组件展示到哪里
下面是app.vue中具体详情:<template> <div> <router-link to="/helloVueRouter">helloVueRouter</router-link> </div> <router-view/> </template> <script> export default { name: 'App' } </script> <style> </style> /* 结合上面的部分我们知道,当我们打开浏览器时,app.vue中的template会替换页面原来的id为app的div标签 在app.vue的template中,使用router-link标签的to属性,去找到到该路径下对应的组件,也就是helloVueRouter组件 这在router/index.js路由配置文件中配置好的,找到该组件后,将组件加载到router-view所在的地方 从而可以在页面中显示helloVueRouter组件的template中的内容 */
vue-router的详解
这节主要讲一些路由的细节信息,包括默认路由,router-link标签的补充,通过代码跳转路由,动态路由,路由的懒加载
配置默认路由
默认路由指的是,最先加载哪个组件,当app.vue的template中有多个router-link标签时
但是因为router-view标签表示一次只能显示一个组件,所以默认路由就是配置哪个组件最先展示。//通过在router/index.js的routes中配置默认路由 const routes = [ { path: '/', redirect: '/helloVueRouter' //重定向 }, { path: '/helloVueRouter', name : 'helloVueRouter', component: helloVueRouter } ] /* 一般默认路由都是配置path为/,这样表示,页面一打开,最先展示哪个组件,上面的例子使用重定向方式 你也可以直接将某个组件的path改成/,表示这是最先加载 { path: '/', name : 'helloVueRouter', component: helloVueRouter } 小提示:app.vue组件并不需要配置路由映射,因为该组件的作用就是使用组件内的template代替页面的div标签 */router-link标签的补充
该标签是一个vue-router中已经内置的组件,它会被渲染成一个a标签,下面是该标签的一些属性:to属性:指定跳转的url路径,该路径对应路由表中某个组件
tag属性:tag可以指定
<router-link>之后渲染成什么标签,比如渲染成li标签,而不是a标签replace属性:该属性不需要任何值,仅仅只是标记这个组件,指定replace的情况下,
浏览器后退键按钮不能返回到上一个页面中,底层应该用的是history对象的replaceState方法更改浏览器url的active-class属性,这个属性的作用是,当组件被展示的时候代表这个组件被激活了,我们的router-link标签
本质上是一个a标签,点击a标签后,组件被激活,此时a标签的class属性值中会有一个router-link-active样式
我们可以通过该样式,对这个a标签做一些处理,比如菜单栏,你点击某一栏,对应的组件被展示出来,
而你点击后的这一栏会更改颜色,就是使用router-link-active样式做的。
而我们的active-class属性则可以设置成其他样式名,改掉router-link-active为其他样式名,
改这个样式名有几种方式,下面总结一下://1,通过router-link标签的active-class属性更改router-link-active样式名 <router-link to="/wanyi" active-class="wanyi">科一</router-link> //2,通过在路由文件中增加linkActiveClass属性,使用该属性,当组件被点击后,会增加对应的样式名 const router = createRouter({ history: createWebHashHistory(), //设置history模式,有很多方式 routes, //路由表 linkActiveClass: 'wanyi' //将router-link-active样式名改成wanyi })
通过代码跳转路由
上面例子都是通过to属性,使得我们的url跳转其他地址,从而替换组件,我们也可以通过代码实现同样的效果
下面让我们来看看如何通过代码方式改变url路径吧<router-link to="/wanyi" active-class="yiwan">科一</router-link> //原来的 <button @click="toWanyi()">科一</button> <script> export default { name: 'App', methods: { toWanyi(){ this.$router.push('/wanyi') } } } </script> /* 简单来说就是使用button+点击事件方式替换了router-link,通过$router.push方法跳转路径 push方法就相当于history对象的pushState方法,浏览器是可以回退到上一个url的, 如果是replace方法,浏览器就不可回退了。这个$router就是我们在router/index.js中创建的路由实例router 这里之所以能使用$router是因为在注册进vue实例时,使用Vue.prototype.$router=router 又因为我们组件都是继承自Vue的原型对象,是Vue构造函数的实例,所以我们组件中可以获取到$router 另外之后你还会见到$route,它指的是当前路由,也就是路由映射关系中那个对象,之后会详细说明 */动态路由
动态路由指的是,我的url路径某一段是不确定的,类似后端的路径变量,我们可以获取路径上这个变量的值
这也算是一种路由传递数据的方式,数据从一个组件,传递到另一个组件。下面是一个简单的例子:原来的: //路由配置文件中,router/index.js { path: '/wanyi', name: 'Wanyi', component: Wanyi } //App.vue <router-link to="/wanyi">科一</router-link> //当我点击跳转时,路径变成/wanyi,会匹配到对应的path,找到Wanyi组件进行渲染 //我想实现,点击跳转时,/wanyi/[id],后面跟上一个变量,这个id变量将来从数据库中获取 现在的: //路由配置文件中 { path: '/wanyi/:id', //这里的:id是变量,接收传递的id值,本例中id=keyi name: 'Wanyi', component: Wanyi //只要路径匹配,就加载Wanyi组件,我们还可以在Wanyi组件中拿到id值 } //App.vue <router-link :to="'/wanyi/'+id" active-class="yiwan">科一</router-link> <script> export default { name: 'App', data(){ return { id: 'keyi' //到时候去数据库查出来的数据 } } } </script>那么,如何在组件中,拿到路径中携带的参数呢?就是上面的例子,Wanyi组件中如何拿到id值?
//修改Wanyi.vue <h3>{{userId}}</h3> //你也可以直接{{$route.params.id}} export default { name: "Wanyi", data(){ return { message: '万一' } }, methods: { say(){ console.log("测试vue-router"); } }, computed:{ //计算属性 userId(){ return this.$route.params.id //关键 } } } /* 这个$route和$router类似,能在组件中使用,因为在vue-router中,将对象赋值给了Vue原型对象 $router代表我们创建的router路由实例,在方法中使用this.$router.push方法不仅可以跳转某个url 还可以携带参数到被激活的组件,而我们这$route则是获取当前路由的数据的,当前路由就是当点击标签后 会根据标签to属性匹配路由表中对应的路径,匹配成功的路径所在的对象就是当前路由对象。 */路由的懒加载
当打包构建应用时,javascript包会变得非常大,影响页面加载,如果我们能把不同路由对应的组件分割成不同的代码块(JS文件)
然后当路由被访问的时候才加载对应组件JS文件,这样就会更加高效
路由中通常有很多组件,这些组件通常会被打包成一个js文件,如果组件太多,这个js就会非常大,当用户进入网站时,
会花费很长时间,甚至出现短暂空白,如何避免呢?使用懒加载,等到激活该组件时,才去加载组件
路由懒加载:主要作用就是将路由对应的组件打包成一个个js代码块,只有在这个路由被访问到的时候才加载对应的组件以前我们写路由配置,总是先导入组件,然后配置到映射对象中,如下: import Home from '../views/Home.vue' const routes = [ { path: '/home', name: 'Home', component: Home } ] 而懒加载的方式是: { path: '/about', name: 'About', //懒加载 component: () => import('../views/About.vue') } 当需要的时候才去导入相应的组件,在打包过程中,这种方式会将组件文件独立成JS文件,等需要的时候再加载。
vue-router嵌套路由
嵌套路由指的是,在路由映射时,一个路径下有多个组件,例如,有两个组件都要在/my路径下,
当我访问/my/news时加载news组件,当我访问/my/message时加载message组件,如果像以前我们得
配置两个路径,通过嵌套路由可以将这两个组件都配置在同一路径下,下面是简单的案例:
使用嵌套路由的步骤:
1)创建对应的子组件,并且在路由映射中配置对应的子路由
//创建MyNews.vue,创建MyMessages.vue
//1.1MyNews.vue,主要看看template
<template>
<div>
<ul>
<li>新闻1</li>
<li>新闻2</li>
<li>新闻3</li>
<li>新闻4</li>
</ul>
</div>
</template>
//1.2MyMessages.vue,主要看看template
<template>
<div>
<ul>
<li>消息1</li>
<li>消息2</li>
<li>消息3</li>
<li>消息4</li>
</ul>
</div>
</template>
//1.3在路由表中的父组件中配置子路由
{
path: '/my',
name: 'My',
component: My,
children: [
{
path: '', //默认路由,这里不需要
component: ()=> import('../views/MyNews.vue')
},
{
path: 'news',
component: ()=> import('../views/MyNews.vue')
},
{
path: 'messages',
component: ()=> import('../views/MyMessages.vue')
}
]
}
2)在组件内部使用<router-view>标签
//在父组件中使用标签,My.vue
<template>
<div>
<router-link to="/my/news">新闻</router-link>
<router-link to="/my/messages">消息</router-link>
<router-view/>
</div>
</template>
vue-router参数传递
参数传递指的是,在路由跳转的时候如何传递参数,传递参数主要有两种类型:params和query
params方式
使用params方式进行参数传递,其router-link中to属性值的对象中必须为name属性不能使用path属性,
不然在目标组件中拿不到params的值,案例如下:<router-link :to="{name: 'Article',params:{id:'123456',name:'万一'},query:{id:'123456',name:'万一'}}">跳转</router-link> 这个Article值就是路由表中目标组件的name属性值 请求形成的路径取决于有没有使用query属性进行传参,如果router-link中使用了query属性进行传参 则请求的路径是/xxx/xxx?id='123456'....,如果没有使用query属性传参,则请求路径就是name指定组件路径 或者path属性指定的路径。 我们可以在跳转后的组件中通过this.$route.params.id或this.$route.query.id拿到传递的数据 params属性用来传递数据,就像post请求传递数据,而query将数据拼接到url上,就像get方式传递数据 注意:params方式只能使用name属性进行参数,不能使用path属性query方式
上面已经有使用query属性进行传参,query属性传参不管是使用name属性还是path属性都可以
通过this.$route.query.xxx拿到数据,个人感觉query是最好的传参方式,当然router-link使用name属性效果也是一样的
当然,除了使用router-link方式传递数据,你也可以使用代码的方式,例如下面方式//在my.vue中 <template> <div> <button @click="toNews">新闻</button> <router-link :to="{path: '/my/messages',query: {name: '消息',id: 2}}">消息</router-link> </div> <router-view/> </template> <script> export default { name: "My", methods: { toNews(){ this.$router.push({path: '/my/news',query: {name: '我的新闻',id: 1}}) } } } </script> /* 使用query类型在路由传递参数时,有两种方式,通过router-link标签或者通过代码方式传递参数 这里$router的push方法可以传递对象,对象中有路径和参数,之前只传了路径 所以总结$router用于传递数据,跳转数据,而$route用于接收数据 值得注意的是:通过router-link标签传递数据时,to属性需要加上:引号,不然报错 */还有一种传递数据的方式,动态路由
动态路由是在路由表的路径中使用:xxx,进行路径的占位,形成xxx/xxx/123
这种方式就像rest风格传递数据,上面已经有动态路由的方式,这种方式和params,query并没有关系。
使用动态路由的方式,我们也可以在目标组件中获取传递的参数,通过this.$route.params.xxx.
vue-router导航守卫
导航守卫指的是:当发生路由跳转时,可以拦截跳转的过程,像拦截器一样的概念,在跳转路由前后做点事
案例:例如我们实现一个需求,当我们点击按钮,切换组件时,将页面的title变成相应的组件名
这里有两种方式实现,第一种使用钩子函数,第二种使用导航守卫
钩子函数
在组件中使用created属性,该属性值是一个钩子函数,当我们点击切换组件时,被切换的组件被创建,并调用created函数
在该函数中改变页面的title,从而实现我们的需求,所有组件实现created函数,在其中设置document.title属性即可实现需求created() { document.title="home" } //小提示,钩子函数指的是,我们编写的函数等着vue去调用导航守卫,我们可以利用路由跳转,组件切换时,必须经过拦截函数,才能切换组件这一特性,
从而改变title值,代码如下://路由表index.js const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', //懒加载 component: () => import('../views/About.vue') }, { path: '/wanyi/:id', name: 'Wanyi', component: Wanyi }, { path: '/my', name: 'My', component: My, children: [ { path: '', component: ()=> import('../views/MyNews.vue') }, { path: '/my/news', //嵌套路径中,这两种子路径写法都可以 component: ()=> import('../views/MyNews.vue') }, { path: 'messages', component: ()=> import('../views/MyMessages.vue') } ] } ] const router = createRouter({ routes, history: createWebHashHistory() // linkActiveClass: 'wanyi' }) //在路由跳转之前,执行该函数 router.beforeEach((to,from,next)=>{ //这个to,和from都是一个个route对象,即路由表中的对象 document.title=to.matched[0].name; //使用matched解决路由嵌套问题 next(); }) export default router /* 上面使用导航守卫调用的方法beforeEach,也叫作前置钩子或者前置守卫,就是说在跳转组件之前被调用 还有一个后置钩子afterEach,这个函数是在跳转组件之后被调用,而且并不需要next方法,因为已经跳转完毕了 上面这两个函数都是全局守卫,还有路由独享的守卫(路由的beforeEnter等属性),组件内的守卫(在组件中beforeRouteEnter等属性) 全局守卫,顾名思义,全局的路由表中只要发生跳转,就会执行拦截函数,而组件内的守卫则时, 切换到这个组件前后才会执行拦截函数,路由独享守卫则是只在该路由中,对该路由下的组件有用。 */
keep-alive的使用
keep-alive主要做组件状态保存的,当我们从一个组件切换到另一个组件,如果我们不进行状态保存,
那么当我们回退到上一个组件时,会重新加载该组件,而如果我们进行状态保存后,当我们回退上一个组件时,会回到原来的状态。
例如我们点外卖,在店铺按钮中点击进入一个店铺,然后我们切换到我的信息,此时再点击店铺按钮时,
还处于之前的店铺,就说明,之前的状态已经被缓存起来了。
keep-alive是vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染
router-view也是一个组件,是vue-router内置的组件,如果直接被包在keep-alive里面,所有路径匹配到的视图组件都会被缓存
组件中有两个钩子函数,activated和deactivated函数,当组件被激活时,activated被调用,当切换到其他组件时deactivated被调用
前提是,router-view被keep-alive包裹住了,即,使用了缓存,如果不使用缓存,这两个钩子函数不会被调用,
不使用缓存,切换组件时,调用的是组件的created和destroyed钩子函数
我们使用keep-alive标签包裹router-view标签后,所有与router-view关联的组件都会被缓存起来
但是我就想这些被缓存起来的组件中,有几个组件不能被缓存起来这个时候,就需要使用到keep-alive的属性
//keep-alive标签属性
include,字符串或正则表达式,只有匹配的组件会被缓存
exclude,字符串或正则表达式,任何匹配的组件都不会被缓存起来
//属性值可以写多个,用逗号隔开,配置好后,则匹配的组件不会被缓存起来
例如:<keep-alive exclude="组件的name属性,组件的name属性"><router-view/></keep-alive>

