vue-router是Vue的路由管理器,它是Vue的核心插件。在当前Vue项目中一般都是单页面应用,所以可以说vue-router它是应用在单页面中的。在web开发中,路由是指根据url分配到对应的处理程序,vue-router它通过对url的管理,实现url和组件的对应,以及通过url进行组件之间的切换。
那什么又是单页面应用(SPA)?一个项目中只有一个完整的html主页面,其它都是html片断组成的分页面。浏览器一开始会在主页面加载所有必须的文件,即第一次进入页面的时候会请求一个html文件,在页面跳转交互时由路由将分页面载入到主页面,这时路径也相应的发生变化,页面内容也发生变化,但是并没有新的html请求。js感知到url的变化后动态的将当前页面的内容清除然后将下一个页面的内容挂载到当前页面上。这时前端就要判断到底是哪个页面进行显示,哪个页面进行清除。这个过程就是单页面应用。与之相对的是多页面应用(MPA),即一个项目是由多个完整的html页面组成,不存在在主分页面的说话,每一次页面跳转所有的资源都要重新进行加载,后台服务器都会返回一个新的html文档。就像传统的页面应用,它们是用超链接来实现页面切换和跳转的。
vue-router它实现的原理就是更新视图但不重新请求页面。下面我们来介绍vue-router的详细用法。
一、安装及使用
1、直接下载:https://unpkg.com/vue-router@3.3.4/dist/vue-router.js & npm: npm install vue-router
2、在index.js中引入vue和vue-router还有页面组件
import Vue from 'vue'
import VueRouter from 'vue-router'
import First from '../components/First.vue'
3、安装Vue.use()插件。 Vue全局使用Router,用来扩展插件。
Vue.use(对象或者函数) 如果是对象会默认调用对象中的install;如果是函数则把这个函数当成install来执行。它应该在调用 new Vue()启动前完成
Vue.use(VueRouter)
4、创建路由对象配置规则。path:路径 name: 路径对应的名字 component: 组件
const routes = [
{
path: '/',
name: 'First',
component: First
},
{
path: '/second',
name: 'Second',
//路由懒加载:只要组件不显示就不去加载对应的组件
component: () => import( '../components/Second.vue')
}
]
5、 创建一个VueRouter,然导出。
const router = new VueRouter({
mode: 'history',
//它是可以自己配置的。基础路径
base: process.env.BASE_URL,
//routes:routes 路由映射表 是一个数组
routes,
})
//导出
export default router
6、将路由对象传递给vue实例。options中加入router:router(可简写成router)
new Vue({
router,
render: h => h(App)
}).$mount('#app')
7、在App.vue中留坑。router-link是router提供的全局组件,是用来提供按钮展示。router-view它也是router提供的全局组件,是用展示对应的组件。
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<!-- 通过传入 `to` 属性指定链接-->
<router-link to="/">First</router-link> |
<!-- 还可以用到:to -->
<!-- <router-link :to="{path:'/'}">First</router-link> -->
<!-- :to后面还可以接name -->
<!-- <router-link :to="{name:'first'}">First</router-link> -->
<router-link to="/second">Second</router-link>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
二、路由模式
vue-router为了实现单页面路由跳转提供了二种模式:hash模式,history模式
hash模式:它是vue-router的默认模式,使用了url的hash来模拟一个完整的url,当url改变时,页面不会去重新的加载。
hash即#它是url的锚点,代表的是网页中的一个位置,如果只是改变#后面的部分,浏览器只会加载相应的内容不会重新载入网页。也就是说,#它是用来指导浏览器动作的,对服务器完全无用,http请求中不包含#,每一次改变#后的部分都会在浏览器的访问历史中增加一个记录,使用“后退”按钮就能回到上一个位置
hash模式它通过锚点值的改变,根据不同的值,渲染指定的DOM位置上不同的数据。
history模式:由于hash模式会在url中自带#,影响美观,这时我们可以用路由的history模式。这种模块利用了history.pushState API来完成url跳转而不用重新加载页面的
// main.js
const router = new VueRouter({
mode:'history',
routes:[......]
})
当使用history模式时,url就像正常的url一样。如:http://localhost:8082/second.但这个需要后台配置支持。
进行配置:要使用history模式需要服务器配置。要在服务端增加一个覆盖所有情况的资源,如果url匹配不到任何静态资源,则要返回一个html页面。下面就设置了如果url输入错误或者是url匹配不到任何资源就返回Home页面。
export const routes = [
{path: "/", name: "homeLink", component:Home}
{path: "/register", name: "registerLink", component: Register},
{path: "/login", name: "loginLink", component: Login},
{path: "*", redirect: "/"}]
上面的path: "*", redirect: "/"。使用了重定向功能。重定是通过routes配置来完成的。重定向的目标可以是一个文件;也可以是一个路由;甚至是一个方法,动态返回重定向目标。
const router = new VueRouter({
routers:[
// 从/a重写向到/b
{ path:'/a',redirect:'/b'},
//它也可以是一个命名的路由
{
path:'/a' ,redirect:{name:'First'}
},
//可以是一个方法
{
path:'/a',redirect:to=>{
//方法接收目标路由作为参数
// retrun 路径+对象
return './first'
}
}
]
})
与重定向redirect相关的还有一个别名alias。使用alias也可以达到redirect的效果。重定指的是当用户访问/a时url将会被替换成/b,然后匹配路由/b。而别名是/a的别名是/b,这说明了当用户访问/b时url会保持/b但是路由匹配则为/a,就像用户访问/a一样的。
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
别名的功能可以将ui结构映射到任意的url,不受限于配置嵌套路由结构。需要注意的是别名不要用在path为'/'中,不然是不起作用的。
三、嵌套路由
路由组件配置中需求增加children字段,字段值结构一致,表示子路由路径。
如果想在Second下添加两个子路由,分别命名为childOne.vue和childTwo.vue。利用vue-router步骤如下:
1、在路由组件配置中增加children字段
const routes = [
{
path: '/second',
name: 'Second',
component: () => import( '../components/Second.vue'),
//嵌套路由
children:[
{
path:'/Second/childOne',
component:() => import( '../components/childOne.vue'),
},
{
path:'/Second/childTwo',
component:() => import( '../components/childTwo.vue'),
}
]
},
{
path:'*',
redirect:'/'
}
]
2、实现子路由路径跳转切换,就要新增一个<router-view>作为新的路由祝口,渲染在children所在的组件里,用<keep-alive>让组件不销毁。
<template>
<div>
<h1>{{msg}}</h1>
<router-link to="/second/childOne">childOne</router-link> ||
<router-link to="/second/childTwo">childTwo</router-link>
<keep-alive include='childTwo'><router-view></router-view></keep-alive>
</div>
</template>
四、导航钩子
vue-router提供的导航钩子主要是用来拦截导航,让它完成跳转或者取消。
有以下三种方式可以植入导航过程
1、全局
全局导航钩子项目中运用较多的是:前置守卫,后置钩子。可以使用router.beforeEach注册一个全局的before钩子
const router = new VueRouter({ ...})
router.beforeEach((to, from, next) => {
//....
})
当一个导航触发时,全局的before钩子按照创建顺序调用 ,钩子是异步解析执行,此时导航在所钩子reslove完一直是一个等待中的状态。
每一个钩子函数接收三个参数,参数的含义如下:
to: 即将要进入的目标路由对象,通俗来讲就是要跳转到的那个路径
from:当前导航正要离开的路由,即从哪个路径来的。
next:Function:一定要调用这个方法来reslove这个钩子,执行效果依赖next方法调用的参数
示例:只是跳转就修改页面的title
要完成这个操作还需求用到路由元信息,那什么又是路由元信息呢?即定义路由的时候可配置meta字段,然后进行访问。
//index.js
const routes = [
{
path: '/',
name: 'First',
component: First,
meta:{
//可以用来存储一些自定义的信息
til:'首页'
},
}
]
//main.js
outer.beforeEach((to, from, next) => {
document.title = to.meta.til || "example";
//console.log(to, from)
next();
})
next()函数传递不同参数有不同的情况,情况如下:
next():进行管理中的下一个钩子,如果全部执行完成那导航的就是确认状态;
next(false):中断当前的导航。若浏览器url改变,那url的地址会重置到from路由对应的地址
next('/')&next({path:'/'}):跳转到一个不同的地址,当前的导航被中断,然后进行一个新的导航
需要注意的一点是:要确保要调用next方法,不然钩子就不会被确认。next函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只有在所有的逻辑路径都不重叠的情况下,不然钩子不会被解析或者是报错。
示例:用户登录(假设已有login登录页面)
router.beforeEach((to, from, next) => {
//需求注意的是:基本身就是要去login路径,这时就不能再next('./login'),所以外面还要再进行一次判断
if (to.path !== './login') {
if (false) {
next()
} else {
next('./login')
}
} else {
next();//不传参正常向下走
}
})
beforeEach有拦截的功能,可以做全局的校验,示例如下:
//使用钩子函数对路由进行权限跳转
router.beforeEach((to, from, next) => {
const role = localStorage.getItem('ms_username');
if (!role && to.path !== './login') {
next('./next');
} else if (to.mata.permission) {
//如果是管理员权限则可进,模拟如下
role == 'admin' ? next() : next('/403');
} else {
next();
}
})
同样的,我们也可以注册一个全局的after钩子,但它不像before钩子那样,after钩子,它没有next方法,不能改变导航。
router.afterEach(route => {
//......
})
2、组件内部的导航钩子
可以在路由组件内直接定义下面的导航钩子
beforeRouteEnter
beforeRouterUpdate
beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
beforeRouteEnter钩子不能访问this,因为钩子在导航确认前被调用,因此即将登场的新组件还没被创建
不过,可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
在 beforeRouteLeave 中直接访问 this。这个 leave 钩子通常用来禁止用户在还未保存修改前突然离开。可以通过 next(false) 来取消导航
beforeRouteLeave的应用。有First,Second,childOne 三个页面,Second页面调到First页面,需要First页面进行缓存,childOne页面调到First页面,First页面不需要缓存
要通过beforeRouteLeave这个钩子来对路由里面的keepAlive进行赋值。从而动态确定First页面是否需要被缓存。
First的路由
{
path: '/',
name: 'First',
component: First,
meta:{
keepAlive:true//需要被缓存
}
}
---------------------------------------------------------------------
second页面
export default {
name: "Second",
data() {
return {}
},
beforeRouteLeave(to,from,next){
to.meta.keepAlive= true;// second跳转到first时,缓存,即不刷新
next();
},
};
---------------------------------------------------------------------
childOne页面
name: "childOne",
data() {
return {}
},
beforeRouteLeave(to,from,next){
to.meta.keepAlive= false;// childone跳到first时,first缓存,即刷新
next();
},
};
3、路由中配置的单个导航钩子
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
},
beforeEnter: (route) => {
// ...
}
}
]
});
五、数据获取
有时候进入到某个路由后,需要从服务器上获取数据,如渲染用户信息时,需要从服务器获取用户的数据,这时可以通过两种方式来完成。
导航完成后获取:先完成导航,然后在接下来的组件生命周期获取数据,在数据获取期间显示正在加载中之类的指示。使用这种方式会马上导航和渲染维护,然后在组件的created钩子中进行获取数据,有机会在数据获取的期间展示一个loading的状态,还可以在不同视图间展示不同的loading状态。
假设有一个 Post 组件,需要基于 $route.params.id 获取文章数据:
<template>
<div class="post">
<div v-if="loading" class="loading">
Loading...
</div>
<div v-if="error" class="error">
{{ error }}
</div>
<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
-----------------------------------------------------
export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
methods: {
fetchData () {
this.error = this.post = null
this.loading = true
getPost(this.$route.params.id, (err, post) => {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}
导航完成之前就获取:在导航完成前,在路由的enter钩子中获取数据,在数据获取成功后执行导航。这种方式在导航转入新的路由前就获取了数据,可以在组件中的beforeRouteEnter钩子中获取数据,当数据获取成功后调用next方法。
export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, (err, post) => {
this.setData(err, post)
next()
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
}
}
}
在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。
六、router中的传参
vue-router传参主要分为两个大类一是编程式的导航,二是声明式的导航。
1、编程式导航(router.push):它传递的参数主要是分为两类,一类是字符串,一类是对象。
为字符串的方式是直接将路由地址用字符串的方式来进行跳转,这种方式简单,但是不能传递参数,如下:
//this.$route这里存储的都是属性
//this.$router这里存储的都是方法
// this.$router.push(参数) 参数等同于to后跟的内容
this.$router.push('/');// /为要跳转的地址
为对象时它主要用到params和query来进行传递,使用query它是拼接到url的后面。可以用name也可以用path进行引入。当First.vue中有一个按钮,点击按钮可以跳转到Second.vue,进行参数传递,代码如下:
First.vue
//使用path引入
methods: {
gosecond: function() {
this.$router.push(
{
path: "second",
query: { id:'123456',name:'tz' }
});
}
}
//使用name引入
methods: {
gosecond: function() {
this.$router.push(
{
name: "second",
query: { id:'123456',name:'tz' }
});
}
},
----------------------------------------------
Second.vue
mounted:function(){
this.id=this.$route.params.id;
this.name = this.$route.params.name
}
使用params有两点需求注意,一是用name来进行引入,所以要确保路由中有name。二是要配置路由中的path,因为path它是路由的一部分,必须在路由后面添加上参数名。
First.vue
<button @click="gosecond">按钮</button>
methods: {
gosecond: function() {
this.$router.push(
{
name: "Second",
params: { id:'123456',name:'tz' }
});
}
}
Second.vue
mounted:function(){
this.id=this.$route.params.id;
this.name = this.$route.params.name
}
index.js
{
path: '/second/:id/:name',
name: 'Second',
component: () => import( '../components/Second.vue')
}
总结编程式导航三种方式:
this.$router.push("destination"),简单但不能传参;
this.$router.push({path(name):"destination",query:{id:xxx,name:xxx}})。
this.$router.push({name:"destination",params:{id:xxx,name:xxx}})。这种方式的不足是如果没有设置路由中的path那在目标页面刷新传递过来的参数不显示会丢失。
2、声明式导航<router-link>
声明式的导航和编程式是一样的。
字符串:
<router-link :to="{path:'/'}">First</router-link>
对象,使用query
<router-link :to="{path:'/',query:{id:'123',name:'first'}}">First</router-link>
对象,使用params
<router-link :to="{path:'/',params:{id:'123',name:'first'}}">First</router-link>