路由简介
Vue Router是Vue官方的路由管理器,专门实现SPA应用,即单页面应用。
单页面应用的含义是,整个应用只有一个完整的页面,点击页面中的链接不会刷新整个页面,只会局部刷新,数据通过ajax请求获取,在页面局部刷新的时候,地址栏的url会跟着变化。
一个路由就是一个key-value的映射关系,key为路径,value为function或component。
路由分为前端路由和后端路由。
前端路由:value为component,用于展示页面内容,浏览器路径改变的时候,对应的组件就会显示。
后端路由:value为function,用于处理客户端提交的请求,服务器接到请求后,根据请求路径找到匹配的函数处理请求,返回响应数据。
路由的基本使用
页面上有两个tab,分别是About和Home,每个tab对应一个路由,也就是需要创建两个组件:About.vue和Home.vue,创建components文件夹,将两个组件放进去。
既然要使用VueRouter,就要安装vue-router,使用npm i vue-router
安装。还需要一个router文件夹,里面放路由的规则index.js。
每个组件都由直接的$route
属性,里面存储着自己的路由信息。
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入VueRouter
import VueRouter from 'vue-router'
//引入路由器
import router from './router'
//关闭Vue的生产提示
Vue.config.productionTip = false;
//应用插件
Vue.use(VueRouter);
//创建vm
new Vue({
el:'#app',
render: h => h(App),
router:router
});
router/index.js
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
import About from '../components/About'
import Home from '../components/Home'
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
});
Home.vue,About.vue类似,只是内容不同
<template>
<h2>我是Home的内容</h2>
</template>
<script>
export default {
name:'Home'
}
</script>
App.vue
<template>
<div>
<div class="row">
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header"><h2>Vue Router Demo</h2></div>
</div>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- 原始html中我们使用a标签实现页面的跳转 -->
<!-- <a class="list-group-item active" href="./about.html">About</a> -->
<!-- <a class="list-group-item" href="./home.html">Home</a> -->
<!-- Vue中借助router-link标签实现路由的切换,经过编译router-link也会被翻译成a标签 -->
<!-- 原来的active的class改为动态添加了,active-class是router-link标签的属性 -->
<!-- to的值对应router/index.js里的key -->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'App',
}
</script>
多级路由
路由是可以嵌套的,也就是在router/index.js里配置即可。和上面不同的是这里使用一个pages文件夹,里面放了4个组件:About.vue、Home.vue、Message.vue、News.vue。普通组件放在components里,路由组件放在pages里。
router/index.js
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
// 创建并暴露一个路由器
export default new VueRouter({
// 一级路由的path带'/',子级路由的path不带'/'
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
// Home组件里还有子级组件,使用children引出
children:[
{
path:'news',
component:News,
},
{
path:'message',
component:Message,
}
]
}
]
});
About.vue没有变,Home.vue里要加上<router-link>
标签,用于路由Message.vue和News.vue。Message.vue和News.vue没有什么新内容,就是普通的页面。
Home.vue
<template>
<div>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li>
<!-- to要带上父组件的前缀 -->
<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
</li>
<li>
<!-- to要带上父组件的前缀 -->
<router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name:'Home',
// 通过函数可以知道,在切换路由的时候,组件是在不断的销毁和新建的
/* beforeDestroy() {
console.log('Home组件即将被销毁了');
}, */
/* mounted() {
console.log('Home组件挂载完毕了',this);
window.homeRoute = this.$route;
window.homeRouter = this.$router;
}, */
}
</script>
路由的query参数
在路由跳转的时候,是支持携带参数的,这里介绍的是query参数,也就是跟在地址的?
后面,用&
分隔的参数。引入一个Detail.vue组件,修改Message.vue,Detail.vue在接收参数的时候使用{{$route.query.key}}
来接收。
Message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 跳转路由并携带query参数,to的字符串写法 -->
<!-- 使用模板字符串和${key}传参 -->
<!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> -->
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Message',
data() {
return {
messageList:[
{id:'001',title:'消息001'},
{id:'002',title:'消息002'},
{id:'003',title:'消息003'}
]
}
},
}
</script>
Detail.vue
<template>
<ul>
<li>消息编号:{{$route.query.id}}</li>
<li>消息标题:{{$route.query.title}}</li>
</ul>
</template>
<script>
export default {
name:'Detail',
mounted() {
console.log(this.$route);
},
}
</script>
命名路由
命名路由就是给路由加一个name属性,当路由的path非常长的时候,用name代替长串路由是比较方便的,需要在定义路由的/router/index.js里给路由指定name。
注意,在<router-link>
中使用命名路由的时候,必须将to属性写成对象的形式。
router/index.js
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'
// 创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News,
},
{
// 给路由起名字
name:'message',
path:'message',
component:Message,
children:[
{
path:'detail',
component:Detail,
}
]
}
]
}
]
});
Home.vue中路由的修改,将path
改为name
即可。
<router-link :to="{
name:'message',
query:{
id:m.id,
title:m.title
}
}">
</router-link>
路由的params参数
params参数是在请求路径上的参数,这里有一点需要注意,路由携带params参数并且<router-link>
的to
写成对象的时候,不能使用path配置项,必须使用name配置,因为使用path的时候,params是带不过来的,使用name可以将path上的参数带过来,在接收params参数的时候使用{{$route.params.key}}
接收。
先来看router/index.js中的路由配置。
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'
// 创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News,
},
{
path:'message',
component:Message,
children:[
{
name:'detail',
path:'detail/:id/:title',
component:Detail,
}
]
}
]
}
]
});
Message.vue中router-link
标签。
<router-link :to="{
name:'detail',
params:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
Detail.vue
<template>
<ul>
<li>消息编号:{{$route.params.id}}</li>
<li>消息标题:{{$route.params.title}}</li>
</ul>
</template>
<script>
export default {
name:'Detail'
}
</script>
路由的props配置
路由还有一个props配置项,用于传值,props有三种写法,写在哪个路由里的props,在哪个路由组件中使用,组件中通过props来接收。
router/index.js部分内容
routes:[
{
path:'detail',
component:Detail,
// props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件
// props:{a:1,b:'hello'}
// props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件
// props:true
// props的第三种写法,值为函数
props($route){
return {
id:$route.query.id,
title:$route.query.title,
a:1,
b:'hello'
}
}
}
]
Detail.vue
<template>
<ul>
<li>消息编号:{{id}}</li>
<li>消息标题:{{title}}</li>
</ul>
</template>
<script>
export default {
name:'Detail',
props:['id','title'],
computed: {
// id(){
// return this.$route.query.id
// },
// title(){
// return this.$route.query.title
// },
}
}
</script>
router-link的replace属性
<router-link>
的replace
属性用于控制路由跳转浏览器历史记录。浏览器历史记录有两种方式:分别是push
和replace
。push
是追加记录,replace
是替换记录,默认采用的是push
,如果需要使用replace
属性,直接加在<router-link>
上即可。
<router-link replace to="/home/news">News</router-link>
编程式路由导航
编程式路由导航就是将导航的前进后退通过自定义方法,调用$router上的方法来实现。
定义几个按钮:push、replace、back、forward、go。
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<router-link :to="{
name:'detail',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
<button @click="pushShow(m)">push查看</button>
<button @click="replaceShow(m)">replace查看</button>
</li>
</ul>
<button @click="back">后退</button>
<button @click="forward">前进</button>
<button @click="test(-1)">测试一下go</button>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Message',
data() {
return {
messageList:[
{id:'001',title:'消息001'},
{id:'002',title:'消息002'},
{id:'003',title:'消息003'}
]
}
},
methods: {
pushShow(m){
this.$router.push({
name:'xiangqing',
query:{
id:m.id,
title:m.title
}
});
},
replaceShow(m){
this.$router.replace({
name:'xiangqing',
query:{
id:m.id,
title:m.title
}
});
},
back(){
this.$router.back();
},
forward(){
this.$router.forward();
},
go(n){
this.$router.go(n);
}
}
}
</script>
缓存路由组件
考虑这么一个场景,有两个tab标签,第一个tab标签里有几个input输入框,输入完成后,点击第二个tab,再点第一个tab,会发现刚刚输入的内容丢失了。这是因为Vue在路由导航切换的时候,默认会销毁历史组件,创建新的组件,这一点可以通过组件的beforeDestroy()
来验证,为了在路由切换的时候,不让组件被销毁,需要一个新的标签<keep-alived>
。
如果News.vue组件需要被缓存,那么,找到News.vue的父组件,也就是Home.vue,在插入News.vue的地方<router-view></router-view>
,包上一层标签<keep-alived>
。<keep-alived>
还有一个属性include
,当<router-view></router-view>
处有可能放多个路由组件的时候,需要用到include
属性,显示指定哪些路由组件需要被缓存,如果不加include
那就是<router-view></router-view>
处的组件都缓存。
Home.vue部分
<!-- 缓存多个路由组件,这里的值是组件名 -->
<!-- <keep-alive :include="['News','Message']"> -->
<!-- 缓存一个路由组件,这里的值是组件名 -->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
两个新的生命周期钩子
这里引出两个路由组件独有的生命周期钩子:activated和deactivated。
在前面提到的路由缓存组件中,添加一个元素和一个定时器,通过定时器修改元素的opacity。这个组件缓存,导致定时器不能被销毁,是非常消耗性能的,于是引出了activated和deactivated,在组件激活的时候,调用activaded()方法,在组件失活的时候,调用deactivaded()方法。
News.vue
<template>
<ul>
<li :style="{opacity}">欢迎学习Vue</li>
<li>news001 <input type="text"></li>
<li>news002 <input type="text"></li>
<li>news003 <input type="text"></li>
</ul>
</template>
<script>
export default {
name:'News',
data() {
return {
opacity:1
}
},
/* beforeDestroy() {
console.log('News组件即将被销毁了');
clearInterval(this.timer);
}, */
/* mounted(){
this.timer = setInterval(() => {
console.log('@');
this.opacity -= 0.01;
if(this.opacity <= 0) this.opacity = 1;
},16);
}, */
activated() {
console.log('News组件被激活了');
this.timer = setInterval(() => {
console.log('@');
this.opacity -= 0.01;
if(this.opacity <= 0) this.opacity = 1;
},16);
},
deactivated() {
console.log('News组件失活了');
clearInterval(this.timer);
}
}
</script>
路由守卫
在进入和离开路由的时候,可以定义一些方法, 就可以在这些时机进行一些操作,对路由进行一些权限控制。
全局前置守卫、全局后置守卫、独享路由守卫写在router/index.js里,组件内路由守卫,写在组件里。在路由里,有一个meta参数,可以让路由以对象的形式接收一些元数据,通过元数据对路由做权限控制。
路由里,会看到3个参数:from、to、next。
from表示从哪里进的路由组件,to表示将要去的路由组件,next是一个方法名,调用next()
表示路由放行,否则路由会停住。
全局前置守卫
router/index.js部分
// 全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
console.log('前置路由守卫',to,from);
if(to.meta.isAuth){/ 判断是否需要鉴权
if(localStorage.getItem('school')==='atguigu'){
next();
}else{
alert('学校名不对,无权限查看!');
}
}else{
next();
}
});
全局后置守卫
router/index.js部分
// 全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
console.log('后置路由守卫',to,from);
document.title = to.meta.title || '硅谷系统';
});
独享路由守卫
独享路由守卫只有一个方法:beforeEnter()
,进入路由时候调用,写在一个路由配置对象的里面。
router/index.js部分
{
name:'news',
path:'news',
component:News,
meta:{isAuth:true,title:'新闻'},
beforeEnter: (to, from, next) => {
console.log('独享路由守卫',to,from);
if(to.meta.isAuth){// 判断是否需要鉴权
if(localStorage.getItem('school')==='atguigu'){
next();
}else{
alert('学校名不对,无权限查看!');
}
}else{
next();
}
}
}
组件内路由守卫
这里有两个方法:beforeRouteEnter()
,beforeRouteLeave()
。
通过路由规则,进入组件和离开组件的时候会被调用。在页面上写一个组件标签,这种不算“通过路由规则”,必须是路由跳转的,地址栏跟着变化的才算。
About.vue
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name:'About',
// 通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
console.log('About--beforeRouteEnter',to,from);
if(to.meta.isAuth){// 判断是否需要鉴权
if(localStorage.getItem('school')==='atguigu'){
next();
}else{
alert('学校名不对,无权限查看!');
}
}else{
next();
}
},
// 通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
console.log('About--beforeRouteLeave',to,from);
next();
}
}
</script>
从a.vue,通过路由规则进入About.vue,会触发beforeRouteEnter()
,从About.vue,通过路由规则进入b.vue,会触发beforeRouteLeave()
。
路由的两种工作模式
在new VueRouter的时候,可以传入一个mode属性,它有两个值,history和hash,默认是hash,标志就是我们在url里看到的#
号。
使用npm run build
对项目进行构建,会生成一个dist文件夹,这个就是由Vue编译后的静态文件,静态文件需要进行部署,才能看到效果,这里使用node express进行部署。
新建一个demo文件夹,使用npm init
初始化,然后一路回车,再输入npm i express
命令安装express。将刚才的文件放到demo下的static里,需要新建一个static文件夹。新建一个server.js文件,使用node server
启动服务。浏览器通过http://localhost:5005/person可以拿到结果,访问http://localhost:5005可以看到vue编译后的首页结果。
const express = requier('express');
const app = express();
app.use(express.static(__dirname + '/static'));
app.get('/person', (request, response)=>{
response.send({
name:'tom',
age:'20'
});
});
app.listen(5005,(error)=>{
if (!error) {
console.log{'服务器启动成功'};
}
});
history模式的路径上不存在#
,而hash模式的路径上存在#
,相比来看,history模式更美观,但是history的兼容性差一点。history和hash的本质区别是发往服务器的请求不一样,hash模式下,#
后面的内容不会被发往服务器,history会把整个路径发往服务器,所以在单页面应用中,会出现这样一种情况:在history模式下,通过路由跳转到一个内页面,再刷新页面会报404。
npm有一个专门的类库解决这个问题。通过npm i connect-history-api-fallback
命令安装这个类库,修改server.js,再启动服务器。
const express = require('express');
const histroy = require('connect-history-api-fallback');
const app = express();
app.use(histroy());// 写在static资源前面
app.use(express.static(__dirname + '/static'));
app.get('/person', (request, response)=>{
response.send({
name:'tom',
age:'20'
});
});
app.listen(5005,(error)=>{
if (!error) {
console.log('服务器启动成功');
}
});