路由Router
1. 相关理解
1.1 vue-router 的理解
vue的一个插件库,专门用来实现SPA单页面应用
1.2 对SPA应用的理解
单页Web应用(single page web application,SPA)
整个应用只有一个完整的页面
点击页面中的导航链接不会刷新页面,只会做页面的局部更新
数据需要通过ajax请求获取
1.3 路由的理解
什么是路由?
一个路由就是一组映射关系(key - value)
key为路径,value可能是function或componen
路由分类
后端路由
理解:value是function,用于处理客户端提交的请求
工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
前端路由
理解:value是component,用于展示页面内容
工作过程:当浏览器的路径改变时,对应的组件就会显示
2. 基本路由
使用步骤
1.安装vue-router
- 命令行下载
npm i vue-router@3 - 注意:vue2需要下载vue-router@3版本,vue3需要下载vue-router@4版本
2.配置main.js
(1)引入VueRouter和router, router是下面需要创建的路由配置文件
main.js
import VueRouter from 'vue-router'
import router from './router' //如果文件夹下只有一个index.js文件,路径写到文件夹就行(2)使用VueRouter
Vue.use(VueRouter)
(3)在new Vue({})中添加router:router配置项,简写router
src/main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router'
Vue.config.productionTip = false
Vue.use(router)
new Vue({
router,
render: h => h(App),
}).$mount('#app')3.创建路由组件
创建pages文件夹,路由组件在此文件夹内创建
路由组件和普通组件一样,单独放在一个文件夹里是为了分类
4.配置router
(1)在src下创建router文件夹并创建index.js文件
(2)引入vue路由VueRouter
import VueRouter from 'vue-router'(3)引入路由组件
import About from '../pages/About'
import Home from '../pages/Home'(4)创建并暴露一个路由器
VueRouter里有一个对象参数,路由以数组的格式存放在routes属性,每个单独路由是一个对象
对象有基本的path,component属性
path是路径
component是组件名
由于网页打开默认路由路径是
"/",没有实际内容,可以在routes数组中添加一个对象,用redirect:'/home'属性强制修改url上的路径
src/router/index.js
import VueRouter from 'vue-router';
import About from '../pages/About'
import Home from '../pages/Home'
export default new VueRouter({
routes:[
{
path:'/', // 网页打开默认路由路径是"/"
redirect:'/home' //强制修改url上的路径为 /home
},
{
path:'/about', //路径前要加 /
component:About
},
{
path:'/home',
component:Home
}
]
})5.实现切换
- 组件内需要添加路由的地方添加
<router-link></router-link>标签,浏览器会被替换为a标签to属性用来设置需要跳转的路由组件路径
active-class属性可配置选中类名,样式需要在css中设置
.active{
color: red;
}
...
<router-link active-class="active" to="/about">About</router-link>6.指定展示位
- 在组件内需要显示路由的地方添加
<router-view></router-view>
3. 几个注意事项
路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹
通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
每个组件都有自己的**$route属性,里面存储着自己的路由信息**
整个应用只有一个router,可以通过组件的**$router**属性获取到
重定向和别名
- redirect:"路径" 重定向/跳转
- alias:''路径别名"
浏览器地址改变后,会在routes中寻找路径对应的路由组件
- 第一个path匹配后,就不会继续走下一个
- 如果找不到,就会404
const router = new VueRouter({
routes: [
{path:"路径" , name:"", component:组件名, }
{path:"路径" , name:"", component:组件名, }
{path:"*" , redirect:"路径", alias:"路径别名"}
]
})path支持通配符*
* 通配,表示匹配所有path
my-* 表示my-开头的就匹配如此,就可以在最后增加一个路由配置,path为"*",再用redirect:'/404'重定向,当网址错误时自动跳转404页面
{ path:"*" , redirect:"/404", component:Err404 }4. 嵌套路由和命名视图
1.嵌套路由
- 配置路由规则,在需要嵌套的组件路径中,使用children属性,children是个数组,子路由组件是对象
步骤
1.创建二级路由组件
- 为了方便整理,可以在pages里再创建一个文件夹,里边存放二级路由组件
2.配置router.js
(1)同样需要先引入路由组件
import Child1 from '../pages/ChildRoute/Child1'
import Child2 from '../pages/ChildRoute/Child2'(2)在一级路由组件的对象内添加children属性,children是个数组,子路由组件是对象
- children里同样有path,和component属性,注意子路由组件的path值中不能加
/
router/index.js
import VueRouter from 'vue-router'
// 引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import Child1 from '../pages/ChildRoute/Child1'
import Child2 from '../pages/ChildRoute/Child2'
// 创建并暴露一个路由器
export default new VueRouter({
routes: [
{
path:'/',
redirect:'/home'
},
{
path: '/home',
component: Home,
children:[
{
path:'child1', // 此处一定不要带斜杠 /
component:Child1
},
{
path:'child2',
component:Child2
}
]
},
{
path: '/about',
component: About
}
]
})3.跳转(要写完整路径)
<router-link to="/home/child1">News</router-link>4.指定展示位
- 组件内需要显示路由的地方添加
<router-view></router-view>
如果要继续嵌套更深层的路由,继续用children无限套娃,工作中顶多套三层
2.命名视图
一个路由,可以显示多个组件,并且组件为兄弟关系
<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>const foo={ template:`<h1>主页面</h1>` }
const navbar={ template:`<h1>导航</h1>` }
const footer={ template:`<h1>页脚</h1>` }
const router = new Router({
routes:[
path:'/',
components:{
default:'foo',
a:navbar,
b:footer
}
]
})5. 命名路由
1.作用:可以简化路由的跳转
2.如何使用
(1)给路由命名
在路由配置中添加name属性
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' // 给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}(2)简化跳转,不用写很长的路径名
注意:想要在to里直接写路由的name,必须给to动态绑定,且内容写成对象的方式
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>
<!--简化写法配合传递参数 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>6. 路由 query 传参
1.传递参数
- 跳转并携带query参数,to的字符串写法
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link>- 跳转并携带query参数,to的对象写法(推荐)
<router-link
:to="{
path:'/home/message/detail',
query:{
id: 1,
title: '标题'
}
}"
>跳转</router-link>2.接收参数
//视图中
$route.query.id
$route.query.title
//方法中
this.$route.query.键7. 路由 params 传参
1.接收params参数
需要在路由配置中,在path路径后用:占位符接收params参数,不同的参数用/分隔
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Msg,
children:[
{
name:'detail',
path:'detail/:id/:title', // 🔴使用:占位符声明接收params参数
component:Detail
}
]
}
]
}2.传递params参数
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
- 跳转并携带params参数,to的字符串写法
<router-link to="/home/message/detail/666/你好">跳转</router-link>- 跳转并携带params参数,to的对象写法
<router-link
:to="{
name:'detail',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>3.接收参数
$route.params.id
$route.params.titleparams和query的区别
query可以通过name属性或者path属性来引入路由,而params只能通过name属性来引入路由;在使用params传递时如果指定了path属性而没有name属性,那么界面能成功跳转但是不能接受到传递过来的参数
在刷新界面时,query传递的参数不会丢失,而params会丢失
直白的来说query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示。
8. 路由的 props 配置
props作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,为true时,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
//基础写法为传入实参$route,也可以使用解构赋值
props($route){
return {
id: $route.query.id,
title: $route.query.title
}
}
}src/router/index.js
import VueRouter from "vue-router";
import Home from '../pages/Home'
import About from '../pages/About'
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:'xiangqing',
path:'detail/:id/:title',
component:Detail,
// props的第一种写法,值为对象,
// 该对象中的所有key-value都会以props的形式传给Detail组件
// props:{a:1,b:'hello'}
// props的第二种写法,值为布尔值,
// 若布尔值为真,会把该路由组件收到的所有params参数,以props的形式传给Detail组件
// props:true
// props的第三种写法,值为函数
props(params) { // 这里可以使用解构赋值
return {
id: params.id,
title: params.title,
}
}
}
]
}
]
}
]
})src/pages/Message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<router-link :to="{
name:'xiangqing',
params:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
</li>
</ul>
<hr/>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'News',
data(){
return{
messageList:[
{id:'001',title:'消息001'},
{id:'002',title:'消息002'},
{id:'003',title:'消息003'}
]
}
}
}
</script>src/pages/Detail.vue
<template>
<ul>
<li>消息编号:{{ id }}</li>
<li>消息标题:{{ title }}</li>
</ul>
</template>
<script>
export default {
name:'Detail',
props:['id','title']
}
</script>9. 路由跳转的 replace 方法
- 作用:控制路由跳转时操作浏览器历史记录的模式,就是js跳转路由
- 浏览器的历史记录有两种写入方式:push和replace
push是追加历史记录,路由跳转时候默认为push方式
replace是替换当前记录
- 开启replace模式
<router-link :replace="true" ...>News</router-link>简写
<router-link replace ...>News</router-link>
总结:浏览记录本质是一个栈,默认push,点开新页面就会在栈顶追加一个地址,后退,栈顶指针向下移动,改为replace就是不追加,而将栈顶地址替换
src/pages/Home.vue
<template>
<div>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li>
<router-link replace class="list-group-item" active-class="active"
to="/home/news">News</router-link>
</li>
<li>
<router-link replace 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'
}
</script>10. 编程式路由导航
作用:实质上是js跳转路由,让路由跳转更加灵活
this.$router.push({}) 内传的对象与<router-link>中的to相同
this.$router.push({
path:'/路径',
query: {参数名:值}
})
this.$router.push({
name:'名称',
params: {参数名:值}
})
获取:this.$route.query/params.参数名this.$router.replace({ })
路由的replace跳转是替换浏览器当前地址记录,也就是替换栈顶
this.$router.resolve({ })
this.$router.forward() 前进,相当于浏览器前进
this.$router.back() 后退,相当于浏览器后退
this.$router.go(n) 可前进也可后退,n为正数前进n,为负数后退
11. 缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁
用法:
- 在需要缓存的组件的父组件里的
<router-view></router-view>外面包含上<keep-alive></keep-alive> - include属性可选择需要缓存的组件
keep-alive可以设置以下props属性:
include- 字符串或正则表达式。只有名称匹配的组件会被缓存exclude- 字符串或正则表达式。任何名称匹配的组件都不会被缓存max- 数字。最多可以缓存多少组件实例
// 缓存一个路由组件
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
// 缓存多个路由组件,注意动态绑定
<keep-alive :include="['News', 'Message']">
<router-view></router-view>
</keep-alive>若要缓存所有组件,则不需要加include
12. 两个新生命周期钩子
activated 路由组件被激活时触发
deactivated 路由组件失活时触发
//...
activated(){
console.log('组件激活')
},
deactivated(){
console.log('组件失活')
}
//...13. 路由守卫
作用:对路由进行权限控制
分类:全局守卫、独享守卫和组件内守卫
1.全局守卫
书写在VueRouter({})内,和routes:[]同级
1.1全局前置守卫
进入下一个组件之前触发
router.beforeEach((to, from, next) => {
// ...
})参数
to: 要进入的目标路由对象(去哪)
from: 要离开的路由对象(从哪来)
next: 是否要进行下一步(要不要继续)
写
next()相当于next(true)继续执行不写 相当于
next(false)终止执行next(path)跳转
next('/login')
// 或者
next({path:'/login'})登录鉴权
permission.js
import router from '@/router'
import store from '@/store'
router.beforeEach((to,from,next)=>{
const whiteList=['/login','/login/sms','/login/token','/404']
if(whiteList.includes(to.path)){
next()
}else{
// store中获取token
const token=store.state.users.token
if(token){
if(!store.state.auths.menu.length){
// 获取菜单列表
store.dispatch('auths/FETCH_MENU')
}
next()
}else{
next({path:'/login'})
}
}
})在main.js中导入
main.js
import "@/permission.js"1.2全局后置守卫
进入下一个组件之后触发,可以用来改变页面的title
router.afterEach((to, from) => {
// ...
})to和from属性和前置守卫一样
注意:后置钩子afterEach没有next参数
1.3 meta
meta是路由route里的一个对象
它可以切换不同页面显示不同的页面标题文字,也可以判断守卫放行的页面
给meta设置isAuth属性可以用来判断是否放行
通过this.$route获取
1.4实例
要使用理由守卫,就要讲路由先创建配置再暴露,不能直接暴露
const router= new VueRouter({
routes: [
{
name:'guanyu',
path: '/about',
component: About,
meta:{title:'关于'}
},
{
path: '/home',
name:'zhuye',
component: Home,
meta:{title:'主页'},
children: [
{
name:'xinwen',
path: 'news', //不需要再加/
component: News,
meta:{isAuth:true,title:'新闻'},
},
{
name:'xiaoxi',
path: 'message',
component: Message,
meta:{isAuth:true,title:'消息'},
children: [
{
name:'xiangqing',
path: 'detail',
component: Detail,
meta:{title:'详情'},
props({query}){
return {
id:query.id,
title:query.title
}
}
}
]
}
]
}
]
})
//全局前置路由守卫-----初始化的时候被调用,每次路由切换被调用
router.beforeEach((to,from,next)=>{
console.log('前置路由守卫',to,from);
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('school')==='atguigu2'){
next()
}else{
alert('您无权通行')
}
}else{
next()
}
})
router.afterEach((to,from)=>{
document.title=to.meta.title||'你好'
})
export default router2. 独享路由守卫
beforeEnter
书写在单独的路由配置里
{
name:'news',
path:'news',
component:News,
beforeEnter:(to,from,next)=>{
console.log('路由独享')
}
}3. 组件内路由守卫
顾名思义,组件内路由守卫需要在组件内书写,并非在router.js文件书写
export default {
name:'About',
...
//通过路由规则,进入该组件时被调用
beforeRouteEnter(to,from,next){
if(to.meta.isAuth){ //判断是否需要鉴权
next() //放行
}
},
//在当前路由改变,但是该组件被复用时调用
beforeRouteUpdate(to,from,next){
...
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave(to,from,next){
next() //放行
}
}14. 路由模式
路由模式有三种,分别为hash,history,abstract
通过router里的mode属性切换路由模式
const router=new VueRouter({
mode:'history',
routes:[...]
})1.hash路由
- 浏览器url中**#及其后面的内容就是hash值**,可通过
location.hash获取 - hash值不会包含在HTTP请求中,即:hash值不会带给服务器。路由默认是hash模式
1.优缺点:
- 地址中永远带着
#号,不美观。 - 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
2.原理
hash路由是由Bom Api中的location.hash改变浏览器地址,通过window.onhashchange事件监听地址栏变化,从而切换不同的组件
通过location.hash切换url时,页面并不会发生任何变化,也不会刷新
<body>
<button class="btn">切换</button>
<script>
document.querySelector(".btn").addEventListener("click", () => {
location.hash = "test";
console.log(location.hash)
});
window.onhashchange = function (e) {
console.log(e.newURL);
};
</script>
</body>2.history路由
1.优缺点
- 地址干净,美观。
- 兼容性和hash模式相比略差,刷新会丢失页面
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
2.原理
语法
history.pushState(stateObject, title, url);参数
| 参数 | 描述 |
|---|---|
| stateObject | 传入的状态对象。当前进(后退)到某一新的状态时,会触发popstate事件。此事件对象event.state存储的就是这个stateObject的值。 |
| title | 新状态的标题。(目前,大多数浏览器并不支持该参数,建议传null值) |
| url | 状态对应的历史记录的地址。 |
<body>
<button class="btn">切换</button>
<script>
document.querySelector(".btn").addEventListener("click", () => {
history.pushState(null, null, "/test");
});
window.onpopstate = function (event) {
console.log(event);
};
</script>
</body>