目录
一、路由概述
路由,其实就是指向的意思,当我点击页面上的 home 按钮时,页面中就要显示home 的内容,如果点击页面上的 about 按钮,页面中就要显示 about 的内容。home 按钮 --> home 内容, about 按钮 --> about 内容,也可以说是一种映射。所以在页面上有两个部分,一个是点击部分,一个是点击之后,显示内容的部分。
点击之后,怎么做到正确的对应,比如,我点击 home 按钮,页面中怎么就正好
能显示 home 的内容。这就要在 js 文件中配置路由。
路由中有三个基本的概念 route、routes、router。
route,是一条路由, home 按钮 --> home内容, 这是一条 route, about 按钮 --> about 内容,这是另一条 route。
routes 是一组路由,把上面的每一条路由组合起来,形成一个数组。[ { home 按钮 -->home内容 }, { about按钮 -->about 内容} ]
router 是一个机制,相当于一个管理者,它来管理路由。因为 routes 只是定义了一组路由,它放在哪里是静止的,当真正来了请求,怎么办? 就是当用户点击home 按钮的时候,怎么办 ?这时 router 就起作用了,它到 routes 中去查找,去找到对应的 home 内容,所以页面中就显示了 home 内容。
客户端中的路由,实际上就是 dom 元素的显示和隐藏。当页面中显示 home 内容的时候,about 中的内容全部隐藏,反之也是一样。客户端路由有两种实现方式:基于 hash 和基于 html5 history api。
vue-router 是 vue 的一个插件库,专门用来实现 SPA 应用。
关于 SPA 应用:
SPA 是一种特殊的 Web 应用,是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的。它将所有的活动局限于一个 Web 页面中,仅在该 Web 页面初始化时加载相应的 HTML 、 JavaScript 、 CSS 。一旦页面加载完成, SPA 不会因为用户的操作而进行页面的重新加载或跳转,而是利用 JavaScript 动态的变换 HTML(采用的是 div 切换显示和隐藏),从而实现UI与用户的交互。在 SPA 应用中,应用加载之后就不会再有整页刷新。相反,展示逻辑预先加载,并有赖于内容 Region(区域)中的视图切换来展示内容。
二、路由分类
路由可以分为前端路由和后端路由。
什么是前端路由?
特点:不向后台发送请求,不刷新页面,前后端分离
前端路由即响应页面内容的任务是由前端来做的,根据不同的url更新页面的内容,随着SPA(单页面应用)的普遍使用,前后端开发分离,项目中基本都使用前端路由,通过路由实现页面的变化。例如,通过 vue 开发的SPA中,切换路由,并不刷新页面,而是根据路由在虚拟 DOM 中加载所需要的数据,实现页面内容的改变。
什么是后端路由?
特点:向服务器发送请求,会刷新页面,前后端不能分离
在浏览器的地址栏中切换不同的 url 时,每次都向后台服务器发出请求,服务器根据不同的响应不同的数据,浏览器接收到数据后再进行渲染,所以后端路由会刷新页面,如果网速慢的话,就会看到一个空白页面等待服务端返回数据,后台路由最大的问题就是不能前后端分离。
三、安装和使用 vue-router
安装:输入 npm install vue-router
使用:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
脚手架创建
使用脚手架创建 vue-router 项目是自动生成 router 文件夹,vue-router 包也会自动给导入,步骤如下
首先在命令行中输入创建 vue 项目的指令
按下回车选择自定义配置
按空格是选中配置,按回车是进入到下一步,我们把 Router 选项选上,然后不断进入下一步即可
最后生成的项目结构如下
比之前的项目多了一个 vue-router 文件夹,下面有个 index.js 文件,里面有对vue-router进行导入
router-link 与 router-view
首先我们来看到其生成的 App.vue 界面,发现其有俩个组件 <router-link></router-link>
,<router-view></router-view>
router-link : 为路由入口,用来设置路由跳转,通过 to 属性指定目标地址,默认渲染成带有正确链接的<a>
标签
router-view : 为路由出口,router-view 根据路由显示组件。在路由切换时,切换的是<router-view>
挂载的组件,其他内容不会发生改变。
路由配置
在router 下 index.js 中,我们来进行路由配置
首先要定义 route, 表示一条路由的实现。它是一个对象,由两个部分组成: path 和 component。path 指路径,component 指的是组件。如:{path:’/home’, component: home}
。
这里是两条路由定义组成一个 routes。
最后创建 router 对路由进行管理,它是由构造函数 new vueRouter()
创建,接受 routes 参数。
const router = new VueRouter({
routes // routes: routes 的简写
})
配置完成后,把router 实例注入到 vue 根实例中,就可以使用路由了,这个在main.js 中进行配置
const app = new Vue({
router
}).$mount('#app')
四、关于前端路由两种模式
路由需要实现三个功能:
- 当浏览器地址变化时,切换页面;
- 点击浏览器【后退】、【前进】按钮,网页内容跟随变化;
- 刷新浏览器,网页加载当前路由对应内容;
在单页面 web 网页中, 单纯的浏览器地址改变, 网页不会重载,如单纯的 hash 网址改变网页不会变化,因此我们的路由主要是通过监听事件,并利用js实现动态改变网页内容,有两种实现方式:
- hash模式:监听浏览器地址 hash 值变化,执行相应的 js 切换网页;
- history模式:利用 history API 实现 url 地址改变,网页内容改变;
它们的区别最明显的就是 hash 会在浏览器地址后面增加#号,而 history 可以自定义地址。
hash模式
使用 window.location.hash 属性及窗口的 onhashchange 事件,可以实现监听浏览器地址 hash 值变化,执行相应的 js 切换网页。下面具体介绍几个使用过程中必须理解的要点:
-
hash指的是地址中#号以及后面的字符,也称为散列值。hash也称作锚点,-本身是用来做页面跳转定位的。如http://localhost/index.html#abc,这里的#abc就是hash;
-
散列值是不会随请求发送到服务器端的,所以改变hash,不会重新加载页面;
-
监听 window 的 hashchange 事件,当散列值改变时,可以通过location.hash来获取和设置hash值;
-
location.hash值的变化会直接反应到浏览器地址栏;
history模式
- window.history 属性指向 History 对象,它表示当前窗口的浏览历史。当发生改变时,只会改变页面的路径,不会刷新页面。
- History 对象保存了当前窗口访问过的所有页面网址。通过 history.length 可以得出当前窗口一共访问过几个网址。
- 由于安全原因,浏览器不允许脚本读取这些地址,但是允许在地址之间导航。
- 浏览器工具栏的【前进】和【后退】按钮,其实就是对 History 对象进行操作。
五、多级路由
配置路由规则,使用 children 配置项。
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news', //此处一定不要写:/news
component:News
},
{
path:'message',//此处一定不要写:/message
component:Message
}
]
}
]
跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
六、路由传参
vue 路由传参是指嵌套路由时父路由向子路由传递参数,否则操作无效。传参方式可以划分为 params 传参和 query 传参,params 传参又可以分为 url 中显示参数和不显示参数两种方式。
1. params 传参
定义路由:
在定义path路由路径时定义参数名和格式,如 path:"/one/login/:num"
,router > index.js 文件如下
注:
:变量
表示这是一个路径参数
/* eslint-disable*/
//第一步:引用vue和vue-router ,Vue.use(VueRouter)
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
//第二步:引用定义好的路由组件
import ChildOne from '../components/childOne'
import ChildTwo from '../components/childTwo'
import Log from '../components/log.vue'
import Reg from '../components/reg.vue'
//第三步:定义路由(路由对象包含路由名、路径、组件、参数、子路由等),每一个路由映射到一个组件
//第四步:通过new Router来创建router实例
export default new Router({
mode: 'history',
routes: [
{
path: '/one',
name: 'ChildOne',
component: ChildOne,
children:[
{
path:'/one/log/:num',
component:Log,
},
{
path:'/one/reg/:num',
component:Reg,
},
],
},
{
path: '/two',
name: 'ChildTwo',
component: ChildTwo
}
]
})
在父路由组件上使用 router-link 进行路由导航,传参用<router-link to="/one/login/001">
的形式向子路由组件传递参数。使用 router-view 进行子路由页面内容渲染,父路由组件 childOne.vue 如下:
<template>
<div style="border:1px solid red;color:red;">
<p>这是父路由childOne对应的组件页面</p>
<p>下面可以点击显示嵌套的子路由 </p>
<router-link to="/one/log/123">显示登录页面</router-link>
<router-link to="/one/reg/002">显示注册页面</router-link>
<router-view></router-view>
</div>
</template>
子路由通过 this.$route.params.num
的形式来获取父路由向子路由传递过来的参数,子路由组件 login.vue 如下:
<template>
<div style="border:1px solid orange;color:orange;">
<p>登录界面:这是另一个嵌套路由的内容</p>
<h3>{{this.$route.params.num}}</h3>
</div>
</template>
1.
$router
为 VueRouter 实例,想要导航到不同URL,则使用$router.push
方法2.
$route
为当前 router 跳转对象,里面可以获取name、path、query、params等
当然,我们也可以不用显式参数进行传参。
定义路由时添加name属性给映射的路径取一个别名,router>index.js文件修改router 如下:
export default new Router({
mode: 'history',
routes: [
{
path: '/one',
name: 'ChildOne',
component: ChildOne,
children:[
{
path:'/one/log/',
name:'Log',
component:Log,
},
{
path:'/one/reg/',
name:'Reg',
component:Reg,
},
],
},
{
path: '/two',
name: 'ChildTwo',
component: ChildTwo
}
]
})
在父路由组件上使用 router-link 进行路由导航,使用 <router-link :to="{name:'home',params:{id:001}}>
形式传递参数。注意 ': to= '
前面的冒号,childOne.vue组件修改如下:
<template>
<div style="border:1px solid red;color:red;">
<p>这是父路由childOne对应的组件页面</p>
<p>下面可以点击显示嵌套的子路由 </p>
<router-link :to="{name:'Log',params:{num:666}}">显示登录页面</router-link>
<router-link :to="{name:'Reg',params:{num:888}}">显示注册页面</router-link>
<router-view></router-view>
</div>
</template>
子路由组件页面获取父路由传参方式不变,reg.vue 文件如下:
<template>
<div style="border:1px solid orange;color:orange;">
<p>登录界面:这是另一个嵌套路由的内容</p>
<h3>子路由获取的参数:{{this.$route.params.num}}</h3>
</div>
</template>
注意:上述这种利用 params 不显示 url 传参的方式会导致在刷新页面的时候,传递的值会丢失。
2. query 传参
定义路由 router>index.js文件如下:
export default new Router({
mode: 'history',
routes: [
{
path: '/one',
name: 'ChildOne',
component: ChildOne,
children:[
{
path:'/one/log/',
component:Log,
},
{
path:'/one/reg/',
component:Reg,
},
],
},
{
path: '/two',
name: 'ChildTwo',
component: ChildTwo
}
]
})
修改路由导航 <router-link :to="{path:'/one/log',query:{num:123}}">
,childOne.vue 文件。
修改如下:
<template>
<div style="border:1px solid red;color:red;">
<p>这是父路由childOne对应的组件页面</p>
<p>下面可以点击显示嵌套的子路由 </p>
<router-link :to="{path:'/one/log',query:{num:123}}">显示登录页面</router-link>
<router-link :to="{path:'/one/reg',query:{num:999}}">显示注册页面</router-link>
<router-view></router-view>
</div>
</template>
子路由组件通过 this.$route.query.num 来显示传递过来的参数,reg.vue 文件如下:
<template>
<div style="border:1px solid purple;color:purple;">
<p>注册界面:这是二级路由页面</p>
<h3>{{this.$route.query.num}}</h3>
</div>
</template>
七、编程式路由导航
脱离<router-link>
标签,实现跳转,让路由跳转更加灵活
//push模式,不破坏历史记录
this.$router.push({
name:'detail',
params:{
id:m.id,
title:m.title
}
})
//replace模式,覆盖历史记录
this.$router.replace({
name:'detail',
params:{
id:m.id,
title:m.title
}
})
路由实现前进后退
//后退
this.$router.back()
//前进
this.$router.forward()
//正数前进,负数后退
this.$router.go()
八、路由守卫
1. 什么是路由守卫
路由守卫就是路由跳转过程中的一些钩子函数 ,在路由跳转的时候,做一些判断或其它的操作。 类似于组件生命周期钩子函数 。
2. 分类
1.全局路由守卫
beforeEach(to, from, next) 全局前置守卫,路由跳转前触发
beforeResolve(to, from, next) 全局解析守卫 在所有组件内守卫和异步路由组件被解析之后触发
afterEach(to, from) 全局后置守卫,路由跳转完成后触发
2.路由独享守卫
beforeEnter(to,from,next) 路由对象单个路由配置 ,单个路由进入前触发
3.组件路由守卫
beforeRouteEnter(to,from,next) 在组件生命周期beforeCreate阶段触发
beforeRouteUpdadte(to,from,next) 当前路由改变时触发
beforeRouteLeave(to,from,next) 导航离开该组件的对应路由时触发
4.参数
to
: 即将要进入的目标路由对象
from
: 即将要离开的路由对象
next(Function)
:是否可以进入某个具体路由,或者是某个具体路由的路径
3. 详解
1.路由前置守卫 beforeEach(to, from, next)
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
在路由跳转前触发,在实际项目中应用最多,主要是登陆验证和跳转权限判断
2.全局解析守卫 beforeResolve(to, from, next)
router.beforeResolve((to, from, next) => {
// ...
})
类似于路由前置守卫 beforeEach(to, from, next),也是路由跳转前触发,但它是同时在所有组件内守卫和异步路由组件被解析之后触发的
调用时机:在 beforeEach(to, from, next)和组件内beforeRouteEnter(to, from, next)之后,afterEach(to, from)之前调用
3.全局后置守卫 afterEach(to, from, next)
router.afterEach((to, from) => {
// ...
})
于路由前置守卫 beforeEach(to, from, next) 相对,路由跳转后触发,但它是同时在所有组件内守卫和异步路由组件被解析之后触发的
调用时机:在 beforeEach(to, from, next) 和组件内 beforeResolve (to, from, next)之后, beforeRouteEnter(to, from) 之前调用
4.路由独享守卫 beforeEnter(to, from, next)
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
于路由前置守卫 beforeEach(to, from, next)相同,但在beforeEach(to, from, next)后触发
5.组件路由守卫 beforeRouteEnter(to, from, next)
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 不能获取组件实例
// 因为当守卫执行前,组件实例还没被创建
}
}
因为该守卫在组件创建之前阶段触发,那个时候组件还没有创建成功,所以这个守卫内不能使用 this 获取组件实例。
调用时机:在全局守卫beforeEach(to, from, next)和独享守卫beforeEnter(to, from, next)之后,全局beforeResolve(to, from, next)和全局afterEach(to, from)之前调用
6.组件路由守卫 beforeRouteUpdate(to, from, next)
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例
},
调用时机:在当前路由复用时
7.组件路由守卫 beforeRouteLeave(to, from, next)
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例
}
通常用来禁止用户在还未保存修改前突然离开
调用时机:导航离开该组件的对应路由时调用
4.完整的导航解析流程
1.触发进入其它路由
2.调用要离开路由的组件守卫beforeRouteLeave
3.调用全局的前置守卫beforeEach
4.在重用的组件里调用 beforeRouteUpdate
5.在路由配置里的单条路由调用 beforeEnter
6.解析异步路由组件
7.在将要进入的路由组件中调用beforeRouteEnter
8.调用全局的解析守卫beforeResolve
9.导航被确认
10.调用全局的后置钩子afterEach
11.触发 DOM 更新mounted
12.执行beforeRouteEnter守卫中传给 next的回调函数