Vue_2 --- 路由
0. 概述
路由(route)就是一组key-value的对应关系,多个路由需要经过路由器(router)的管理,为了实现SPA(single page web application)应用,即单页面应用
被导入的组件叫做一般组件,放在components中,由router控制的组件叫做路由组件,放在pages目录中
使用路由切换组件时,销毁老的组件,会走销毁的生命周期,挂载新的组件
1. 环境搭建
1. 下载
# vue2使用vue-router3
npm install vue-router@3
2. 目录创建
src/router/index.js
import VueRouter from "vue-router";
import About from "@/pages/About.vue";
import Home from "@/pages/Home.vue";
// 创建并暴露一个router
export default new VueRouter({
routes: [
{
path: "/about",
component: About
},
{
path: "/home",
component: Home
},
]
})
3. 导入并使用
main.js
import Vue from 'vue'
import App from './App.vue'
// 1. 导入VueRouter插件
import VueRouter from "vue-router";
// 2. 导入router对象
import router from "@/router";
Vue.config.productionTip = false
// 3.使用VueRouter插件
Vue.use(VueRouter)
new Vue({
render: h => h(App),
router // 4. 配置router对象
}).$mount('#app')
2. 一级路由
App.vue
<template>
<div id="app" class="warpper">
<div class="menu">
<!-- 1. 要想实现路由跳转,需要使用router-link标签 -->
<router-link to="/about">我的</router-link>
<router-link to="/home">关于</router-link>
<!-- 2. 指定组件呈现的位置 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
.warpper{
display: flex;
}
.menu{
block-ellipsis: auto;
}
</style>
src/pages/Home.vue
<template>
<h2>我是Home组件</h2>
</template>
<script>
export default {
name: "Home",
}
</script>
src/pages/About.vue
<template>
<h2>我是About组件</h2>
</template>
<script>
export default {
name: "Home",
}
</script>
3. 嵌套路由(多级路由)
src/router/index.js
import VueRouter from "vue-router";
import About from "@/pages/About.vue";
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import message from "@/pages/message.vue";
export default new VueRouter({
routes: [
{
path: "/about",
component: About
},
{
path: "/home",
component: Home,
children:[ // 子路由列表
{
path: "news", // 子路由不需要加/
component: News
},
{
path: "message",
component: message
},
]
},
]
})
src/App.vue
<template>
<div id="app">
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<router-link class="list-group-item" to="/about">About</router-link>
<router-link class="list-group-item" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<div>
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
components: {}
}
</script>
<style>
</style>
src/pages/Home.vue
<template>
<div>
<h2>这里是Home的内容</h2>
<ul class="nav nav-tabs">
<li>
<!--子路由需要加上父路由前缀,即/home/news-->
<router-link class="list-group-item" to="/home/news">News</router-link>
<router-link class="list-group-item" to="/home/message">message</router-link>
</li>
<router-view></router-view>
</ul>
</div>
</template>
<script>
export default {
name: "Home"
}
</script>
<style scoped>
</style>
src/pages/News.vue
<template>
<div>
<h2>这里是News的内容</h2>
<ul>
<li>news001</li>
<li>news002</li>
<li>news003</li>
</ul>
</div>
</template>
<script>
export default {
name: "News"
}
</script>
<style scoped>
</style>
4. 命名路由
简化路由跳转的路径字符串
src/router/index.js
import VueRouter from "vue-router";
import About from "@/pages/About.vue";
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import Message from "@/pages/Message.vue";
import Detail from "@/pages/Detail.vue";
export default new VueRouter({
routes: [
{
name: "about", // 一级路由的命名
path: "/about",
component: About
},
{
path: "/home",
component: Home,
children: [
{
path: "news",
component: News
},
{
name: "message", // 二级路由的命名
path: "message",
component: Message,
children: [
{
name: "detail", // 三级路由的命名
path: "detail",
component: Detail
},
]
},
]
},
]
})
1. 一级路由的简写
src/pages/App.vue
<template>
<div id="app">
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- 一级路由没必要这么写,/about更简单些 -->
<!-- <router-link class="list-group-item" to="/about">About</router-link>-->
<router-link class="list-group-item" :to="{name:'about'}">About</router-link>
<router-link class="list-group-item" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<div>
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
components: {}
}
</script>
<style>
</style>
2. 二级路由的简写
src/pages/Home.vue
<template>
<div>
<h2>这里是Home的内容</h2>
<ul class="nav nav-tabs">
<li>
<!-- 二级路由的简写 -->
<!-- <router-link class="list-group-item" to="/home/news">News</router-link>-->
<router-link class="list-group-item" :to="{name:'news'">News</router-link>
<router-link class="list-group-item" to="/home/message">Message</router-link>
</li>
<router-view></router-view>
</ul>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Home"
}
</script>
<style scoped>
</style>
3. 三级路由的简写
src/pages/Message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<router-link :to="{
// path:'/home/message/detail',
name:'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: "Messages",
data() {
return {
messageList: [
{id: "001", title: "message001"},
{id: "002", title: "message002"},
{id: "003", title: "message003"},
]
}
}
}
</script>
<style scoped>
</style>
5. 路由传参
1. query参数
src/router/index.js
import VueRouter from "vue-router";
import About from "@/pages/About.vue";
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import Message from "@/pages/Message.vue";
import Detail from "@/pages/Detail.vue";
export default new VueRouter({
routes: [
{
path: "/about",
component: About
},
{
path: "/home",
component: Home,
children:[
{
path: "news",
component: News
},
{
path: "message",
component: Message,
children:[
{
path: "detail", // 详情页
component: Detail
},
]
},
]
},
]
})
src/pages/Message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 1. 跳转路由并携带query参数,to=""的模版字符串写法 -->
<!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>-->
<!-- 2. 跳转路由并携带query参数,to=""的对象写法 -->
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}">
</router-link>
</li>
</ul>
<hr>
<!-- router-view表示将内容展示在这里 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Message",
data() {
return {
messageList: [
{id: "001", title: "message001"},
{id: "002", title: "message002"},
{id: "003", title: "message003"},
]
}
}
}
</script>
<style scoped>
</style>
src/pages/Detail.vue
<template>
<div>
<ul>
<!-- this.$route.query 获取传过来的query参数 -->
<li>消息编号:{{$route.query.id}}</li>
<li>消息内容:{{$route.query.title}}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Deatil",
}
</script>
<style scoped>
</style>
2. params参数
src/router/index.js
import VueRouter from "vue-router";
import About from "@/pages/About.vue";
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import Message from "@/pages/Message.vue";
import Detail from "@/pages/Detail.vue";
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", // 如果使用to=""的对象写法,必须使用命名路由方式,模板字符串都可以
path: "detail/:id/:title", // 1. 声明detail后携带两个param参数,一个是id,一个是title
component: Detail
},
]
},
]
},
]
})
src/pages/Message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 1. 跳转路由并携带query参数,to=""的模版字符串写法 -->
<!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link>-->
<!-- 2. 跳转路由并携带query参数,to=""的对象写法 -->
<router-link :to="{
name:'detail', // 如果使用的是对象写法,必须使用命名路由,路由必须配置name
params:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
</li>
</ul>
<hr>
<!-- router-view表示将内容展示在这里 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Messages",
data() {
return {
messageList: [
{id: "001", title: "message001"},
{id: "002", title: "message002"},
{id: "003", title: "message003"},
]
}
}
}
</script>
<style scoped>
</style>
src/pages/Detail.vue
<template>
<div>
<ul>
<!-- this.$route.params 获取传过来的params参数 -->
<li>消息编号:{{$route.params.id}}</li>
<li>消息内容:{{$route.params.title}}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Deatil",
}
</script>
<style scoped>
</style>
6. 路由 props 配置
用于简化读取params和query参数的代码,谁接收参数在谁的路由中写props配置项
src/router/index.js
import VueRouter from "vue-router";
import About from "@/pages/About.vue";
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import Message from "@/pages/Message.vue";
import Detail from "@/pages/Detail.vue";
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,
// 第一种写法: 该对象中的所有key-value都会以props的形式,传给Detail组件,但是只能传死数据,一般不用
// props:{a:1,b:"hello"}
// 第二种写法: 若改配置为true,就会将该路由组件收到的所有params参数,,以props的形式传给Detail组件
// query参数不可以
// props:true
// 第三种写法:常用
// query参数和params参数都可以
props($route) {
// return {
// id: $route.query.id,
// title: $route.query.title
// }
return {
id: $route.params.id,
title: $route.params.title
}
}
// 解构赋值
// props({query}) {
// return {id: query.id, title: query.title}
// }
// props({param}) {
// return {id: param.id, title: param.title}
// }
// props({param: {id, title}}) { // 解构赋值后再结构复制
// return {id, title}
// }
},
]
},
]
},
]
})
src/pages/Detail.vue
<template>
<div>
<ul>
<!-- this.$route.query 获取传过来的query参数 -->
<li>消息编号:{{id}}</li>
<li>消息内容:{{title}}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Deatil",
props: ["id", "title"] // 接收props参数
}
</script>
<style scoped>
</style>
7. 路由记录模式
1. push模式
控制路由跳转时,操作浏览器历史记录的模式,以栈的方式被浏览器存储,router-link 默认是push模式,即追加记录
<!-- 默认是push模式 -->
<router-link class="list-group-item" :to="{name:'news'}">News</router-link>
2. replace模式
控制路由跳转时,操作浏览器历史记录的模式,以栈的方式被浏览器存储,可以开启 router-link 的replace模式,即替换记录
<!-- 以:replace=true 来替换为replace模式,简写为replace -->
<router-link replace class="list-group-item" :to="{name:'news'}">News</router-link>
8. 编程式路由导航(不借助router-link)
1. 指定路由跳转
src/pages/Detail.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<router-link :to="{
name:'detail',
params:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
<!-- 给按钮绑定点击事件,并且通过$router来push或replace操作路由跳转记录 -->
<button @click="pushShow(m)">push查看</button>
<button @click="replaceShow(m)">replace查看</button>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Messages",
data() {
return {
messageList: [
{id: "001", title: "message001"},
{id: "002", title: "message002"},
{id: "003", title: "message003"},
]
}
},
methods: {
pushShow(m) {
// 指定往detail路径跳转,并将当前路由记录以push的模式压入栈中
this.$router.push({
name: 'detail',
params: {
id: m.id,
title: m.title
}
})
},
replaceShow(m) {
// 指定往detail路径跳转,并将当前路由记录以replace的模式替换栈顶的记录
this.$router.replace({
name: 'detail',
params: {
id: m.id,
title: m.title
}
})
}
}
}
</script>
<style scoped>
</style>
2. 前进/后退
App.vue
<template>
<div id="app">
<button @click="back">后退</button>
<button @click="forward">前进</button>
<button @click="forwardThree">指定前进3次</button>
<button @click="backThree">指定后退3次</button>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<router-link class="list-group-item" to="/about">About</router-link>
<router-link class="list-group-item" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<div>
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
back() {
// 后退一步
this.$router.back()
},
forward() {
// 前进一步
this.$router.forward()
},
forwardThree(){
// 参数如果是整数代表forward() 3 次
this.$router.go(3)
},
backThree() {
// 参数如果是负数代表back() 3次
this.$router.go(-3)
}
}
}
</script>
<style>
</style>
9. 缓存路由组件
用户在News组件中输入了一些内容,切换其他组件再切换回来以后发现News中的内容不见了,是因为切换了组件,销毁了原组件,那么如何缓存之前的组件呢,需要在News组件的父组件中编写代码来控制
src/pages/Home.vue
<template>
<div>
<h2>这里是Home的内容</h2>
<ul class="nav nav-tabs">
<li>
<router-link class="list-group-item" to="/home/news">News</router-link>
<router-link class="list-group-item" to="/home/message">Message</router-link>
</li>
<!-- 1. 在router-view展示内容的地方使用 keep-alive 标签包裹起来,使用include来指定缓存哪个路由组件中的name声明的组件名,缓存一个直接写字符串 -->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
<!-- 2. 缓存多个,使用数组 -->
<keep-alive :include="['News','message']">
<router-view></router-view>
</keep-alive>
</ul>
</div>
</template>
<script>
export default {
name: "Home"
}
</script>
<style scoped>
</style>
10. 路由独有的生命周期钩子
当News组件中绑定了定时器,同时News组件被缓存了,当切换到其他路由的时候,此时定时器还在执行中,效率很低
1. activated() -- 激活
<template>
<div>
<h2 :style="{opacity}">这里是News的内容</h2>
<ul>
<li>news001 <input type="text"></li>
<li>news002 <input type="text"></li>
<li>news003 <input type="text"></li>
</ul>
</div>
</template>
<script>
export default {
name: "News",
data() {
return {
opacity: 1
}
},
// 1. 激活news路由的时候开启定时器开始闪烁
activated() {
this.timer = setInterval(() => {
console.log("@")
this.opacity -= 0.1
if (this.opacity <= 0.1) this.opacity = 1
}, 16)
},
deactivated() {
clearInterval(this.timer)
}
}
</script>
<style scoped>
</style>
2. deactivated() -- 失活
<template>
<div>
<h2 :style="{opacity}">这里是News的内容</h2>
<ul>
<li>news001 <input type="text"></li>
<li>news002 <input type="text"></li>
<li>news003 <input type="text"></li>
</ul>
</div>
</template>
<script>
export default {
name: "News",
data() {
return {
opacity: 1
}
},
activated() {
this.timer = setInterval(() => {
console.log("@")
this.opacity -= 0.1
if (this.opacity <= 0.1) this.opacity = 1
}, 16)
},
// 1. news路由失活的时候清除定时器
deactivated() {
clearInterval(this.timer)
}
}
</script>
<style scoped>
</style>
11. 路由守卫
只有符合某些条件才可以进行路由跳转
1. 全局前置路由守卫
src/router/index.js
import VueRouter from "vue-router";
import About from "@/pages/About.vue";
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import Message from "@/pages/Message.vue";
import Detail from "@/pages/Detail.vue";
const router = new VueRouter({
routes: [
{
name: "about",
path: "/about",
component: About,
},
{
name: "home",
path: "/home",
component: Home,
children: [
{
name: "news",
path: "news",
component: News,
meta: { // 路由元信息,即程序员定义的一系列信息,可以用来配置表示这个路由是否开启路由守卫校验,谁需要校验,谁就配置
isAuth: true
}
},
{
name: "message",
path: "message",
component: Message,
meta: { // 路由元信息,即程序员定义的一系列信息,可以用来配置表示这个路由是否开启路由守卫校验,谁需要校验,谁就配置
isAuth: true
},
children: [
{
name: "detail",
path: "detail/:id/:title",
component: Detail,
props($route) {
return {id: $route.params.id, title: $route.params.title}
}
},
]
},
]
},
]
})
// 全局前置路由守卫,每次路由切换前被调用,初始化时被调用
router.beforeEach((to, from, next) => {
// to 目标路由, from 从哪个路由来的,next() 放行
// if (to.path !== "/home") {
// if (localStorage.getItem("school") === "一中") next()
// }
if (to.meta.isAuth) {
if (localStorage.getItem("school") === "一中") next()
}else{
next()
}
})
export default router
2. 全局后置路由守卫
通常用于切换页签标题
import VueRouter from "vue-router";
import About from "@/pages/About.vue";
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import Message from "@/pages/Message.vue";
import Detail from "@/pages/Detail.vue";
const router = new VueRouter({
routes: [
{
name: "about",
path: "/about",
component: About,
meta: {
title: "关于" // 配置页签标题
}
},
{
name: "home",
path: "/home",
component: Home,
meta: {
title: "主页" // 配置页签标题
},
children: [
{
name: "news",
path: "news",
component: News,
meta: {
isAuth: true,
title: "新闻" // 配置页签标题
}
},
{
name: "message",
path: "message",
component: Message,
meta: {
isAuth: true,
title: "消息" // 配置页签标题
},
children: [
{
name: "detail",
path: "detail/:id/:title",
component: Detail,
meta: {
isAuth: true,
title: "详情" // 配置页签标题
},
props($route) {
return {id: $route.params.id, title: $route.params.title}
}
},
]
},
]
},
]
})
router.beforeEach((to, from, next) => {
if (to.meta.isAuth) {
if (localStorage.getItem("school") === "一中") next()
}else{
document.title = to.meta.title || "监控系统"
next()
}
})
// 后置路由守卫,每次路由切换后被调用,初始化时被调用
router.afterEach((to,) => {
// to 目标路由, from 从哪个路由来的
document.title = to.meta.title || "监控系统" // 设置页签标题
})
export default router
public/index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- CSS -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.2/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<title>监控系统</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
3. 独享守卫
src/router/index.js
import VueRouter from "vue-router";
import About from "@/pages/About.vue";
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import Message from "@/pages/Message.vue";
import Detail from "@/pages/Detail.vue";
const router = new VueRouter({
routes: [
{
name: "about",
path: "/about",
component: About,
meta: {
title: "关于"
}
},
{
name: "home",
path: "/home",
component: Home,
meta: {
title: "主页"
},
children: [
{
name: "news",
path: "news",
component: News,
meta: {
isAuth: true,
title: "新闻"
},
beforeEnter: (to, from, next) => { // News组件的独享路由守卫,只有前置没有后置
if (to.meta.isAuth) {
if (localStorage.getItem("school") === "一中") next()
}else{
next()
}
}
},
{
name: "message",
path: "message",
component: Message,
meta: {
isAuth: true,
title: "消息"
},
children: [
{
name: "detail",
path: "detail/:id/:title",
component: Detail,
meta: {
isAuth: true,
title: "详情"
},
props($route) {
return {id: $route.params.id, title: $route.params.title}
}
},
]
},
]
},
]
})
router.beforeEach((to, from, next) => {
// if (to.meta.isAuth) {
// if (localStorage.getItem("school") === "一中") next()
// }else{
// next()
// }
next()
})
router.afterEach((to,) => {
document.title = to.meta.title || "监控系统"
})
export default router
4. 组件内路由守卫
src/pages/About.vue
<template>
<div>
<h2>这里是About的内容</h2>
</div>
</template>
<script>
export default {
name: "About",
// 1. 通过路由规则进入该组件时调用,必须点击router-link标签
beforeRouteEnter(to,from,next){
next()
},
// 2. 通过路由规则离开该组件时调用,必须点击router-link标签
beforeRouteLeave(to,from,next){
next()
}
}
</script>
<style scoped>
</style>
12. 路由器工作模式
1. hash
Vue中/#/后的所有东西,都算作是hash值,/#/包括后面的所有东西都不会发送给服务器
http://localhost:8080/#/a/b/ 服务器接收到的请求为: /,/#/及以后得所有东西都是前端使用的
2. history
没有hash值,但是所有的路径都会发送给服务器
页面点击没问题,当页面刷新时,会出现404的问题,
1. 通过后端的路由规则解决这个问题,这里以Django为例
urls.py
from django.views.generic import TemplateView
from django.urls import path
urlpatterns = [
# ...其他Django路由...
# Vue.js 应用的路由
path('', TemplateView.as_view(template_name='index.html')),
]
2. 通过nginx解决这个问题
location /{
error_page 404 /index.html; # 将404页面转交给index.html处理
}
3. 修改工作模式
import VueRouter from "vue-router";
const router = new VueRouter({
mode:"history", // 指定路由系统的工作模式,默认为hash模式
})
export default router
4. 对比
hash模式:
- 地址栏中永远带着#号,不美观
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
- 兼容性较好
histroy模式:
- 地址栏干净,美观
- histroy模式比hash模式的兼容性略差
- 应用部署上线时,需要通过后端人员或nginx配置,解决刷新页面服务端404的问题

浙公网安备 33010602011771号