Vue Router(路由)

70 阅读3分钟

1.路由route与路由器router

  • 路由route
  • 路由器:router
  • 每一个路由都由key和value组成
    • key1+value1 ===> 路由route1
    • key2+value2 ===> 路由route2
    • key3+value3 ===> 路由route3
  • 路由的本质:一个路由表达了一组对应关系
  • 路由器的本质:管理多组对应关系
  • vue中路由的工作原理: image.png

2.使用路由

1. 实现功能描述

  • image.png image.png
  • 根据静态页面提取两个组件:Tea.vue和Fruit.vue image.png image.png image.png

2.vue-router插件安装

  • vue2 安装的是:vue-router3
    • npm i vue-router@3
  • vue3安装的是:vue-router@4
  • main.js中引入和使用vue-router
    • 导入:`import VueRouter from 'vue-router'
    • 使用:Vue.use(VueRouter)
    • new Vue时添加新的配置项:一旦使用了vue-router插件,在new Vue的时候可以添加一个全新的配置项:router image.png
  • router路由器的创建一般是放在一个独立的js文件中,例如:router/index.js
    • 创建router目录
    • 创建index.js,在index.js中创建路由对象,并将其暴露,然后在main.js文件引入该路由器 image.png
  • 使用router-link标签代替a标签(App.vue)中 image.png
<!-- 如果使用的是路由方式,就不能使用超链接a标签了,需要使用vue-router插件提供的一个标签 -->
<!-- router-link 将来会被自动编译为a标签。 -->
<li><router-link to="/hebei" active-class="selected">河北省</router-link></li>
<li><router-link to="/henan" active-class="selected">河南省</router-link></li>
  • router-link标签最终编译之后的还是a标签.vue-router库帮我们完成的
  • 添加激活样式
    • 使用active-class属性,在激活时添加的样式:selected image.png
  • 指定组件的最终显示位置 image.png
<!-- 路由视图,其实就是起到一个占位的作用。 -->
 <router-view></router-view>
  • 测试代码最终效果: image.png image.png image.png
  • 注意事项:
    • 路由组件一般会和普通组件分开存放,路由组件放到pages目录,普通组件放到compons目录下
    • 路由组件在进行切换的时候,切换的组件会被销毁
    • 路由组件实例比普通组件实例多两个属性:$route#router
      • $route:属于自己的路由对象
      • $router:多组件共享的路由器对象

// 导入vue-router插件
import VueRouter from "vue-router"

// 导入组件
import HeBei from '../components/HeBei'
import HeNan from '../components/HeNan'

// 创建路由器对象(在路由器对象中配置路由。)
const router = new VueRouter({
    // 在这里配置所有的路由规则。
    routes : [
        // 这就是一个路由
        {
            // 只要路径监测到的是 /hebei,就切换到HeBei组件.
            // 这个可以看做就是路由的key
            path : '/hebei',
            // 这个可以看做就是路由的value,路由的value是一个组件。
            component : HeBei
        },
        {
            // 注意:这个路径要以 / 开始。
            path : '/henan',
            component : HeNan
        }
    ]
})

// 暴露路由器对象(导出路由器对象)
export default router

3.多级路由

1.实现的效果

image.png

image.png

  • 主要实现的代码: image.png image.png image.png

2.注意部分:

const router = new VueRouter({
    routes : [
        {
            path : '/hebei',
            component : HeBei,
            // 子路由们
            children : [
                // 这是其中的一个子路由
                {
                    // 注意:对于子路由来说,这个path不要以 / 开始,这个 / 系统会自动添加。
                    path : 'shijiazhuang',
                    component : ShiJiaZhuang
                },
                {
                    path : 'handan',
                    component : HanDan
                }
            ]
        },
        {
            path : '/henan',
            component : HeNan
        }
    ]
})
export default router

3.路由query传参

  • 为了提高组件的复用性,可以给路由组件传参
  • 怎么传?

image.png

image.png

  • 怎么接? image.png
// index.js
const router = new VueRouter({
    routes : [
        {
            path : '/hebei',
            component : HeBei,
            children : [
                // 子路由(路由对象)
                {
                    path : 'city',
                    component : City
                }
            ]
        },
        {
            path : '/henan',
            component : HeNan
        }
    ]
})

// HeBei组件
<!-- 采用query方式传参,主要使用对象形式 -->
                <li>
                    <router-link active-class="selected" :to="{
                        path : '/hebei/city',
                        query : {
                            a1 : sjz[0],
                            a2 : sjz[1],
                            a3 : sjz[2],
                        }
                    }">
                        石家庄
                    </router-link>
                </li>
                
 // City组件
          <li v-for="areaName,propertyName in $route.query" :key="propertyName">
                {{areaName}}
            </li>

 <script>
    export default {
        name :'City',
        mounted() {
            // 所有的路由组件都有一个属性 $route,通过这个属性可以获取到该“路由组件”关联的“路由对象”
            console.log(this.$route)
            // 路由对象中有一个query属性,这个query属性可以接收query方式传递过来的数据。
            console.log(this.$route.query)
        },
    }
</script>

4.路由起名字

  • 通过给路由命名,可以简化to的编写
    • 怎么起名? image.png
  • 怎么使用?必须使用`:to="{}"的方式 image.png

5.路由params传参

  • 怎么接? image.png

image.png

  • 怎么传?

image.png

  • 如果使用params传参,使用:to的时候,只能有name,不能使用path
<router-link replace :to="{  
// 使用params传参必须使用name传,不能用path  
// name的值根据router里面的name值来设置  
name:'Rao',  
params:{  
// a1代表属性名,SR代表data里面的数据  
s1:SR[0],  
s2:SR[1],  
s3:SR[2],  
}  
}">上饶市</router-link>

6.路由的props

  • props配置主要是为了简化queryparams参数的接收,让插值语法更加简洁
  • 第一种实现方式: image.png

image.png

  • 第二种实现方式:函数式

image.png

image.png

  • 第三种实现方式:直接将params方式接收到的数据转换为props image.png image.png
// index.js
routes:[  
//子路由们
children:[  
{  
// 设置路由的名字  
name:'Rao',  
// :代表属性名  
path:'ShangRao/:s1/:s2/:s3',  
component:City,  
props:true  
},  
{  
name: "Nan",  
/*  
* :/动态路由参数,可以匹配任意字符串,并将匹配到字符串作为参数传递给组件  
* 组件通过 this.$route.params来访问路由参数对象  
* */  
path: "Nan/:n1/:n2/:n3",  
component: City,  
// props配置  
/*  
* props配置将路由参数传递给组件,通过在路由配置中定义props  
* 然后在路由参数作为值传递给组件,通过调用props值,  
* 然后将组件渲染到页面  
* props有对象试,和函数式两种写法  
* */  
// 对象试  
/* props:{  
x:1,y:1  
}*/  
// 函数式  
/* props($route){  
// $route 会自动传递调用组件里面的值  
return{  
s1:$route.params.s1,  
s2:$route.params.s2,  
s3:$route.params.s3  
}  
}*/  
// 当用params参数作为路由向组件传递时,可以只写一个ture  
// 表示组件自动调用params  
// query不行  
props:true  
}  
]  

7.router-link的replace属性

  • 栈数据结构:先进后出,后进先出原则(子弹夹)

image.png

  • 浏览器的历史记录是存储在栈这种数据结构当中.包括两种模式:
    • push模式(默认)
    • replace模式

image.png

  • 如何开启replace模式:
    • <router-link :replace=”true”/>
    • <router-link replace />
<!-- 开启replace模式入栈内存  
压栈的时候直接覆盖,不是在栈顶添加元素  
-->  
<router-link replace :to="{  
// 使用params传参必须使用name传,不能用path  
// name的值根据router里面的name值来设置  
name:'Rao',  
  
params:{  
// a1代表属性名,SR代表data里面的数据  
s1:SR[0],  
s2:SR[1],  
s3:SR[2],  
}  
}">上饶市  
</router-link>

8.编程式路由导航

  • 需求中可能不是通过点击超链接的方式切换路由,也就是说不使用 <router-link>
  • 如何实现路由切换.可以通过相关的API来完成
    • push模式:
this.$router.push({

name : ‘’,

query : {}

})
  • replace模式:
this.$router.replace({

name : ‘’,

query : {}

})
  • 前进:
    • this.$router.forward()
  • 后退:
    • this.$router.back()
  • 前进或后退几步
    • this.$router.go(2) 前进两步
    • this.$router.go(-2) 后退两步
  • 使用编程式路由导航的时候,需要注意:重复执行 push 或者 replace 的 API 时,会出现以下错误:

image.png

  • 这是因为push方法返回是一个promise对象,所以你在调用push方法时候传递两个回调函数.
  • 一个是成功的回调,一个是失败的回调,如果不传就会出现以上错误
  • 以解决以上问题只需要给 push 和 replace 方法在参数上添加两个回调即可
methods:{  
/*  
* 函数式路由导航  
* 通过这里编写的代码,可以完成路由组件的切换  
* 这里编程方式完成路由组件的切换,称为编程式路由导航  
* 通过调用现有的API就可以完成路由组件的切换  
* 这里获取的是路由器对象,不是路由器  
* this.$route 获取路由对象  
* this.$router 获取路由器对象(路由器对象一般一个项目只需要一个)  
* */  
Nc(){  
// 使用push模式传递,push压栈的时候会从下面添加一个新的元素  
this.$router.push({  
// 这里使用query传参,query传参可以用name,也可以用path  
// :/表示动态路由参数,可以通过:/将参数名传递给相对应的组件  
path:'Nan/:n1/:n2/:n3',  
query:{  
// this表示vm实例  
n1:this.NC[0],  
n2:this.NC[1],  
n3:this.NC[0],  
  
}  
/*  
* 定义了两个箭头函数,表示通过promise对象返回两个参数,一个是成功的参数,一个是失败的参数  
* 如果不定义者两个回调函数,则会在路由调用时候出现错误  
* */  
},()=>{},()=>{})  
},  
Sr(){  
// 使用replace压栈,会进入最后一个元素时,会直接替换栈顶的元素  
this.$router.replace({  
/*  
* name 代表路由的标识符,可以通过name找到对应的组件  
* 在params中,只能使用name标识符来传递组件的数据  
* */  
name:'Rao',  
params:{  
s1:this.SR[0],  
s2:this.SR[1],  
s3:this.SR[2],  
}  
},() =>{},() =>{})  
}  
}  
}

9.缓存路由组件

  • 默认情况下路由切换时,路由组件会被销毁。有时需要在切换路由组件时保留组件(缓存起来)
<keep-alive inclue=”组件名称”>

<router-view/>

</keep-alive>
  • 这里的组件名称指的是: image.png
  • 不写 include 时:<router-view>包含的所有路由组件全部缓存

image.png

<!-- 该组件表示,这个标签在切换的时候,所有的路由组件都不会被销毁-->  
<keep-alive include="JiangSu">  
<router-view></router-view>  
</keep-alive>  
<!-- include表示指定的组件不会被销毁 -->  
  
<keep-alive include="JiangSu">  
<router-view></router-view>  
</keep-alive>  
  
<!--include可以使用数组式,表示多个-->  
<keep-alive :include="[`JiangSu`,`JiangXi`]">  
<router-view></router-view>  
</keep-alive>  
</div>

10.activated 和 deactivated

  • 这是两个生命周期钩子函数
  • 只有路由组件才有的两个生命周期钩子函数
  • 路由组件被切换到的时候调用:activated
  • 路由组件被切换走的时候调用:deactivated
  • 这两个钩子函数作用是捕获路由组件的激活状态
  • 普通组件有九个生命钩子:
    • 8个生命周期函数 + this.nextTick(fuction())
    • this.nextTick(fuction()):该函数在下一次DOM元素渲染时候执行
  • 路由组件有两个新的生命钩子 + 九个生命钩子:
    • activated 路由组件被激活时候执行
    • deactivated 在路由组件被切换的时候执行

11.路由守卫

1.全局前置路由守卫

  • router/index.js 文件中拿到 router 对象
  • router.beforeEach((to, from, next)=>{ // 翻译为:每次前(寓意:每一次切换路由之前执行。)
    • to:去哪里(to.path,to.name)
    • form:从哪来
    • next继续:调用next() image.png
  • 这种路由守卫称为全局前置路由守卫
  • 初始化执行一次,以后每一次切换路由之前调用一次
  • 如果路由组件较多,to.path会比较繁琐,可以考虑给需要鉴权的路由扩展一个布尔值属性,可以通过路由元来定义属性:
    • meta:{isAuth : true}

image.png

//index.js
routes: [
{  
path: "/JiangXi",  
component: JiangXi,  
children: [  
{  
// 设置路由的名字  
name: 'Rao',  
// :代表属性名  
path: 'ShangRao/:s1/:s2/:s3',  
component: City,  
props: true,  
/*  
* meta 存储页面的元数据  
* 是一个对象,可以包含任意你想要的属性,如标题,描述,权限等  
* 在路由配置中,可以为每个路由设置一个meta对象,然后在路由导航守卫中访问这些信息  
* 通过beforeEach导航守卫检查该用户是否有权限访问该路由  
* */  
meta: {  
isLogin: true  
}  
}
]

/*  
* 全局前置路由守卫  
* 全局前置路由守卫代码写在哪里? 在创建好router之后,暴露之前  
* beforeEach中的callback什么时候调用?  
* 在初始化时候执行一次,以后在每一次切换路由时候被调用  
* callback可以是普通函数,也可以试箭头函数  
* callback三个参数(to,from,next)  
* from参数:form是一个路由对象,表示从哪儿来(从哪个路由切过来) 起点  
* to参数:是一个路由对象.表示到哪里去,终点  
* 通过to可以访问路由对象里面的参数  
* next参数:这是一个函数,调用这个函数之后,表示放行,可以继续向下执行  
* */  
router.beforeEach((to, from, next) =>{  
let logiName = "fuxu";  
if (to.meta.isLogin){  
if (logiName === "fuxu"){  
next()  
}else {  
alert(`权限不足`)  
}  
}else {  
next()  
}  
})

2.全局后置守卫

  • router/index.js 文件中拿到 router 对象
router.afterEach((to, from)=>{ // 翻译为:每次后(寓意:每一次切换路由后执行。)

// 没有 next

document.title = to.meta.title // 通常使用后置守卫完成路由切换时 title 的切换。

})
  • 这种路由守卫称为全局后置路由守卫
  • 初始化时执行一次,以后每一次切换路由之后调用一次
    • 该功能也可以通过前置路由守卫实现 image.png
  • 该功能使用后置守卫实现更好

image.png

  • 解决闪烁问题:

image.png

/*  
* 全局后置路由守卫  
* 代码 写在哪里?router对象之后,export之前  
* afterEach中的回调函数什么时候执行?初始化之前执行一次,组件切换完成后调用  
* 回调函数有两个参数:to,from  
* to:目标路径,from当前路径  
* 这个回调函数没有next参数  
* */  
// 全局后置路由  
router.afterEach((to, from) =>{  
document.title = to.meta.title || "欢迎光临"  
})

3.局部路由守卫之 component 守卫

image.png

  • 注意:只有路由组件才有这两个钩子
/*  
* 代码写在哪里?局部路由守卫之component守卫,代码写在component中(写在组件当中的xxx.vue)  
*beforeRouteEnter执行时机:进入路由组件之前被调用  
* beforeRouteLeave执行时机:离开路由组件之前被调用  
* component守卫只有路由组件才会触发这两个钩子函数  
* */  
beforeRouteEnter(to,form,next ){  
console.log(`进入路由组件前:${to.meta.title}`)  
next()  
},  
beforeRouteLeave(to,from,next){  
console.log(`离开路由组件前:${from.meta.title}`)  
next()  
  
}  
  
}

4.局部路由守卫之 path 守卫

image.png

  • 注意:没有afterEnter
routes: [  
  
        {  
        path: "/JiangXi",  
        component: JiangXi,  
        children: [  
        {  
        // 设置路由的名字  
        name: 'Rao',  
        // :代表属性名  
        path: 'ShangRao/:s1/:s2/:s3',  
        component: City,  
        props: true,  
        /*  
        * meta 存储页面的元数据  
        * 是一个对象,可以包含任意你想要的属性,如标题,描述,权限等  
        * 在路由配置中,可以为每个路由设置一个meta对象,然后在路由导航守卫中访问这些信息  
        * 通过beforeEach导航守卫检查该用户是否有权限访问该路由  
        * */  
        meta: {  
        isLogin: true,  
        title:"上饶"  
        },  
        /*  
        * beforeEnter局部路由守卫之path守卫,代码写在哪里?写到route对象中  
        *beforeEnter本身是一个函数,所以不能用箭头函数写形参,只能用普通函数写形参  
        * beforeEnter三个参数:to,from,next  
        * to:目标路由,form:起始路由,next()对组件是否放行  
        * beforeEnter什么时候调用?进入"Rao"(当前路由)这个路由时候调用  
        * */  
        beforeEnter(to,from,next){  
        const logName = 'jq';  
        if (logName === `jq`){  
        next()  
        }else {  
        next('/JiangXi')  
                }  
            }  
         }
]