文章目录
1.路由是什么
首先我们需要搞清楚路由是什么东西
摘自维基百科:通过互联的网络把信息从源地址传输到目的地址的活动.
其实也就是说,当我们在浏览器中的地址栏输入不同的url时,浏览器为我们展示不同的页面时,这就是一个路由的过程。
路由中其实包括有一个比较重要的概念叫做路由表:路由表的本质就是一个映射表,它决定了我们数据的指向。
路由也分为两个时期,在这两个时期,都有各自的方式来实现路由。这两个时期分别为后端路由和前端路由。
1.1 后端路由
在早期的网站开发时,前后端是在一个服务器开发的,也就是说我们常说的服务器端渲染。
当用户向服务器发送请求,服务器再根据url返回不同的html页面给前端。当我们页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户。
这一操作就是后端路由。
优点: 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于SEO的优化.
缺点: 过于混乱,前后端分工不明确,服务器压力大
1.2 前端路由
对于前端路由来说,路由的映射函数通常是进行一些DOM的显示和隐藏操作。这样,当访问不同的路径的时候,会显示不同的页面组件。也就是说我们如何实现改变url但是页面不进行整体的刷新。
前端路由的出现要从 Ajax开始,有了 Ajax 后,用户交互就不用每次都刷新页面,体验带来了极大的提升。随着技术的发展,简单的异步已经不能满足需求,所以异步的更高级体验出现了——SPA(单页面富应用)
其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由。也就是前端来维护一套路由规则
前端路由的实现:
实现方式有两种,一种是hash,一种是利用HTML5中BOM的history。
通过这两种方式改变url但是页面不会进行刷新。
hash:
早期的前端路由的实现就是基于location.hash来实现的。其实现原理也很简单,location.hash的值就是URL中#后面的内容。
实例:
location.hash='bar'
History API:
主要可以使用方法pushState()和replaceState(),前者将我们的路由值压入栈中,然后可以使用go等方式进行来回访问,但是后者只能替换当前的路由,没有history可以替换。
history.pushState({},'','a')
history.pushState({},'','b')
history.pushState({},'','c')
history.go(-2)//a
2. vue-router基本使用
vue-router是Vue官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。路由用于设定访问路径, 将路径和组件映射起来.
在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.
安装(使用脚手架的话我们直接可以选择初始化这些操作):
npm install vue-router --save
2.1 搭建路由的框架:
- 第一步:导入路由对象,并且调用 Vue.use(VueRouter)下载插件
- 第二步:创建路由实例,并且传入路由映射配置
- 第三步:在Vue实例中挂载创建的路由实例
router代码:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
]
})
挂载实例代码:
import router from './router'
new Vue({
el: '#app',
router,
render: h => h(App)
})
2.2 配置路由的映射关系
- 第一步: 创建路由组件
- 第二步: 配置路由映射: 组件和路径映射关系
- 第三步: 使用路由: 通过和
配置路由组件:
routes: [
{
path: '/hello',
component: HelloWorld
},
{
path:'/home',
component:home
},
{
path:'/about',
component:about
}
]
使用路由:
<template>
<div id="app">
<router-link to="hello">hello</router-link>
<router-link to="home">home</router-link>
<router-link to="about">about</router-link>
<router-view></router-view>
</div>
</template>
注意:
- : 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个标签
- : 该标签会根据当前的路径, 动态渲染出不同的组件.
- 网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和处于同一个等级.
关于router-link标签的其他操作:
router-link标签还可以指定渲染成不通过的标签,可以使用tag属性进行设定。
router-link标签的默认跳转行为是push(出入栈)的形式,我们可以直接进行指定replace属性进行改变为replace的属性。
<router-link to="about" tag="button" replace="">about</router-link>
2.3 设置默认路由
默认路由就是当我们刚进入页面时展示的路由,这需要我们再router中配置映射关系,只需要加入一个配置即可
{
path:'/',
redirect:'/home'
},
2.4 改变前端路由模式
再默认情况下,我们vue-router使用的时hash的方式来进行路由处理,前文说到,我们路由操作有两种方式,我们也可以通过下面代码将路由的模式改变为history的方式:
只需要再创建router实例的地方,增加mode参数即可
export default new Router({
routes: [
],
mode:'history'
})
2.6 使用代码跳转路由
如果我们在开发过程中不想使用router-link标签,我们这里可以自己定义一个标签,然后自己加逻辑来实现路由跳转操作。
其中使用的是Router插件为我们提供的全局的一个属性$router.该属性可以直接指定通过push方法和replace方法来进行指定路由进行跳转。
实例:
aboutclick() {
this.$router.push("/about");
},
注意:在使用这种方式跳转时,在Vue-Router 3.1的版本以前是没有什么问题的,但是在以后$router.push()`方法改为了Promise。所以假如没有回调函数,错误信息就会交给全局的路由错误处理,因此就会报以下的错误。
[NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated"}]
有两种方法可以解决:
- 切换版本(=没说)
- 禁止全局路由错误处理打印,这个也是vue-router开发者给出的解决方案:在定义VueRouter的地方添加以下代码
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
2.7 动态路由
如果学习过nodejs或者了解一些后端知识的话,会发现这里的动态路由和其是非常相像的,它不需要我们给其传入一个固定的值,这一场景可能会出现在商品的分类展示上,当用户点击相应的入口时我们路径可能是:/product/man或者/product/women。
这种方式除了我们固定的product以外,后面还跟了product的细分,再通过这些展示不同的数据。我们也将这一操作称为动态路由。
这需要我们在设置映射关系时这样操作:
{
path:'/product/:classify',
component:person
}
然后在使用时加入我们的动态内容:
<router-link :to="'/product/' + classify">home</router-link>//classify为动态数据
其实这样就可以实现动态路由了。如果我们还想再组件中输出我们的classify的变量,可以使用$route属性,这个属性是获取当前活跃的路由信息。
<p>{{ $route.params.userid }}</p>
2.8 路由懒加载
关于路由的懒加载,官方给我们的解释为:
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
其实就是说我们的每一个路由对应的就是一个给用户展示的页面,然而按照常规的操作这些页面最终会通过webpack打包为一个bundle.js的文件,因此就造成了这个页面十分的庞大,所在在请求的时候可能会花费一定的时间,给用户造成不好的体验。因此我们可以通过路由懒加载的方式来解决这个问题。也就是说不同的路由页面打包成不同的js文件,这样就解决了服务器耗时的问题。
在我们未使用懒加载时,打包vue项目会生成三个文件,一个是app的所有业务代码,一个是组织管理模块关系的代码,还有一个就是项目引入第三方的代码。
接着我们看一下实现懒加载的方式:有三种方式分别是结合异步组件的方式,AMD的方式,es6模块化的方式
在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.所以我们就是用最简单的方式:
component: () => import('../components/about.vue')
实例:
routes: [
{
path: '/',
redirect: '/home'
},
{
path: '/hello',
component: () =>import('../components/HelloWorld.vue')
},
{
path: '/home',
component: () => import('../components/home.vue')
},
{
path: '/about',
component: () => import('../components/about.vue')
},
{
path: '/person/:userid',
component: () => import('../components/person.vue')
}
],
这样一来的最终的打包结果:
我们发现每个路由界面都有一个js文件,这样一来就可以做到我们按需加载的要求,减轻了服务器的压力
3. vue-router嵌套路由
为什么要嵌套路由,当我们的路由组件中需要嵌套不同的界面时,比如我们的选项卡切换操作就是最好的使用场景
我们的大概操作流程就是:
- 先定义好我们的子路由组件
- 在路由映射表中的父路由中使用children属性定义子路由
- 在父路由足见中使用子路由(正常使用)
看一个栗子:
这里我们的需求是在home路由中嵌套两个组件分别是homemessage和homenews,然后分别通过/home/homemessage和/home/homenews来访问他们的内容
在映射表中定义子路由实现:
{
path: '/home',
component: () => import('../components/home.vue'),
children:[
//设置默认路由
{
path:'',
redirect:'homemessage'
},
{
path: 'homemessage',
component: () => import('../components/homemessage.vue'),
},
{
path: 'homenews',
component: () => import('../components/homenews.vue'),
}
]
},
我们这里均使用懒加载进行导入组件。这里还需要注意的是在子路由的path中,地址前面是不能加‘/’
的。
在父路由中使用:
<router-link to="/home/homemessage">消息</router-link>
<router-link to="/home/homenews">新闻</router-link>
<router-view></router-view>
4. vue-router参数传递
4.1 参数传递的两种方式
关于路由的参数传递问题大概一共有两种方式
第一种就是在动态路由时我们讲过的使用字符串拼接的形式从跳转页传递参数,再使用$route.params.参数名
进行接受
第二种就是我们现在要将的使用query的形式。
首先我们再跳转时可以在标签上动态绑定一个对象,给其传入path和query对象,其中query对象的内容就是我们想要传入data的内容(如下示例所示):
<router-link
:to="{
path: '/about',
query: {
id: '001',
name: '张三',
sex: '男',
},
}"
>about</router-link
>
接着第二步就是使用$route.query
接收使用其参数
<p>{{ $route.query}}</p>
<p>{{ $route.query.name}}</p>
...
这就是我们router种参数的传递。
r o u t e 和 route和 route和router的区别:
r o u t e r 为 V u e R o u t e r 实 例 , 想 要 导 航 到 不 同 U R L , 则 使 用 router为VueRouter实例,想要导航到不同URL,则使用 router为VueRouter实例,想要导航到不同URL,则使用router.push方法
$route为当前router跳转对象里面可以获取name、path、query、params等
5. vue-router导航守卫
导航守卫的作用一般是用来监听我们当前的路由进出情况。
我们可以想象一个场景,在一个spa页面我们如果切换路由界面时,想要title实时刷新的话,我们应该怎么做。
其实我首先想到的是在利用每一个路由的生命周期函数,在刚开始create组件的时候来使用document.title
来进行title的转换。
但是这样做是不是有点太low了,并且比较繁杂,这时就可以使用我们的前置导航守卫beforeEach
。
这是router实例的一个方法,参数是一个含有三个参数的函数。分别to、from、next。根据源码我们可以看出to和from都是router类型,一因此就可以操作他们来进行我们的router内的操作。
export type NavigationGuard<V extends Vue = Vue> = (
to: Route,
from: Route,
next: NavigationGuardNext<V>
) => any
所以关于我们上面的需求,我们就可以这样做:
router.beforeEach((to,from,next) => {
next();
console.log(to);
document.title=to.matched[0].meta.title
})
meta需要在router的映射表里设置路由原信息meta相关的数据。
关于to返回的数据我们可以看一下:
注意这里我们最好使用matched数组来进行设置相关信息,因为这是我们一个路由及其里面所有的嵌套路由的实例。如果你确定你的router里没有嵌套的router,那么你可以使用外层的mate属性来进行设置。
**补充:**除了前置导航守卫以外我们其实还有后置导航守卫,路由导航守卫,以及组件内的导航守卫等等。我们可以在不同的场景下来使用不同的守卫。。
6. 使用keep-alive缓存路由界面
keep-alive经常被用来缓存组件,从而达到节省性能的目的。
当然keep-alive也可以缓存我们的路由,我们的路由界面其实都是一个组件,而每个组件又是一个vue的实例,vue的实例又遵循vue的声明周期,因此当我们切走当前的路由界面时,我们当前的组件也会被销毁,当第二次再进入时再去从create开始创建我们的组件。
因此就有了一个明显的需求。当我们再a路由界面执行了某些操作后,再切入b页面,当b页面的操作做完以后我们想要切回a继续做之之前的事情。如果我们没有使用keep-alive的时候会发现我们不会达到上面的需求。
当然业务不同需求不同,也有的需要我进入时重新构建dom。
所以我们来看看用法:
<keep-alive>
<router-view></router-view>
</keep-alive>
其实只需要router-view
上层包裹一层keep-alive
标签就可以达到我们缓存的效果了。
在keep-alive里还有连个类似于声明周期函数的两个钩子函数deactivated和destroyed函数。
分别是在组件活跃和不活跃时调用。所以我们也就可以在deactivated函数里面操作一些一进入界面就需要操作的一些业务。
还有关于includ
和exclude
的使用,分别是通过组件的name属性指定:
<keep-alive include="home,about">
<router-view></router-view>
</keep-alive>
<keep-alive exclude="hello">
<router-view></router-view>
</keep-alive>