6、路由

什么是路由

路由的本质就是一些keyvalue组成的映射关系,每一个key都会指向一个value,多个路由(route)需要通过一个路由器(router)进行管理。

vue中的路由,目的就是实现单页面应用(SPA - single page web application),是vue的一个插件库。

搭建路由环境

1、安装vue-router

注意vue-router@4只能在vue3中使用,vue-router@3可以在vue2中使用

cnpm i vue-router@3

2、src下新建router/index.js,Vue使用VueRouter,并且配置router实例

// 该文件用于创建整个应用的route
import VueRouter from "vue-router"
import Vue from 'vue'
//使用VueRouter
Vue.use(VueRouter);

//创建并暴露一个VueRouter实例
export default new VueRouter({
    routes: []
})

3、main.js中引入router,并配置到Vue实例

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false
//引入router
import router from './router'

new Vue({
    render: h => h(App),
    //配置router到Vue实例
    router
}).$mount("#app")

简单使用

router/index.js中引入要切换的组件,并配置路径

import VueRouter from "vue-router"
import Vue from 'vue'

Vue.use(VueRouter);

import Home from '../view/Home.vue'
import About from '../view/About.vue'

export default new VueRouter({
    routes: [
        //重定向根路径/到/home
        {
          	path: "/",
            redirect: "/home";
        },
        {
            path: "/home",
            component: Home
        },
        {
            path: "/about",
            component: About
        }
    ]
})

两个组件Home.vueAbout.vue

<template>
    <div>
        is About
    </div>
</template>
<script>
export default {
    name: "About",
}
</script>
<template>
    <div>
        is Home
    </div>
</template>
<script>
export default {
    name: "Home",
}
</script>

App.vue中创建了两个路由链接和路由展示标签

<template>
  <div>
    <!-- roter-link相当于a标签,只不过href需要换成to,值就是roter中配置的path -->
    <router-link to="/home" active-class="ac">Home</router-link>/
    <!-- replace:本次的浏览记录会替换掉上次的 -->  
    <router-link replace to="/about" active-class="ac">About</router-link><br>
    <!-- 用于展示路由的标签 -->
    <router-view/>
  </div>
</template>

<script>

export default {
    name: "App"
}
</script>
<style scoped>
/* 选中样式 */
.ac{
  color: red;
}
</style>

注意点

1、开发中,一般组件和路由组件通常放在src下不同的文件夹中,易于区分。一般情况下,一般组件放在components文件夹下,路由组件放在views文件夹下

2、组件在切换的时候才会进行创建挂载、切换到其他组件后,当前组件将被销毁

3、在使用了vue-route插件后,所有的组件实例对象上,都会多两个属性$route(保存当前路由信息)、$router(全局路由实例)

嵌套路由

router/index.js

import VueRouter from "vue-router"
import Vue from 'vue'

Vue.use(VueRouter);

import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
import Ab1 from '../pages/Ab1.vue'
import Ab2 from '../pages/Ab2.vue'

export default new VueRouter({
    routes: [
        {
            path: "/home",
            component: Home
        },
        {
            path: "/about",
            component: About,
            //使用children属性声明子路由
            children:[
                {   // 注意:对于子路由,路由不需要加/
                    path: "ab1",
                    component: Ab1
                },
                {
                    path: "ab2",
                    component: Ab2
                }
            ]
        }
    ]
})

About.vue进行子路由的跳转和展示

<template>
    <div>
        is About<br>
        <router-link to="/about/ab1">ab1</router-link>/
        <router-link to="/about/ab2">ab2</router-link>
        <router-view/>
    </div>
</template>
<script>
export default {
    name: "About",
}
</script>

App.vue

<template>
  <div>
    <router-link to="/home" active-class="ac">Home</router-link>/
    <router-link to="/about" active-class="ac">About</router-link><br>
    <router-view/>
  </div>
</template>
<script>
export default {
    name: "App",
}
</script>
<style scoped>
/* 选中样式 */
.ac{
  color: red;
}
</style>

命名路由

命名路由用来简化路由跳转时的完整路径(只是简化编码,并不简化地址栏显示)

import VueRouter from "vue-router"
import Vue from 'vue'

Vue.use(VueRouter);

import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
import Info from '../pages/Info.vue'

export default new VueRouter({
    routes: [
        {
            path: "/home",
            component: Home
        },
        {
            path: "/about",
            component: About,
            children:[
                {	
                    //配置命名路由
                    name: "abInfo",
                    path: "info",
                    component: Info
                }
            ]
        }
    ]
})

使用命名路由

<!-- 跳转的时候,就可以直接写路由的name值 -->
<router-link to="abInfo">info</router-link><br>

路由传参

query

可以对路由组件进行传参

方式一:拼接路径字符串

这种方法适合传少量的参数

About.vue,通过路径给子路由传参

<template>
    <div>
        学生信息<br>
        <!-- 利用路由跳转路径传参 -->
        <router-link :to="'/about/info?id=' + s.id + '&name=' + s.name + '&age=' + s.age" v-for="s in stu" :key="s.id">
            {{s.name}}
            <br/>
        </router-link>
        <router-view/>
    </div>
</template>
<script>
export default {
    name: "About",
    data() {
        return {
            stu: [
                {id: "001",name: "lucy",age: "18"},
                {id: "002",name: "tom",age: "22"},
                {id: "003",name: "lily",age: "16"},
                {id: "004",name: "james",age: "23"},
                {id: "005",name: "marry",age: "17"},
            ],
        };
    },
}
</script>

Info.vue,通过this.$route.query接收路径中的参数

<template>
    <div>
        <!-- 通过this.$route.query可以获取路径中拼接的参数 -->
        编号:{{$route.query.id}}<br/>
        姓名:{{$route.query.name}}<br/>
        年龄:{{$route.query.age}}<br/>
    </div>
</template>
<script>
export default {
    name: "Ab1",
}
</script>

方式二:to的对象写法

这种方法可以传任何的对象

About.vue

<template>
    <div>
        学生信息<br>
        <!-- 使用对象进行传递参数 -->
        <router-link :to="{path:'/about/info',query:{stu:s}}" v-for="s in stu" :key="s.id">
            {{s.name}}
            <br/>
        </router-link>
        <router-view/>
    </div>
</template>
<script>
export default {
    name: "About",
    data() {
        return {
            stu: [
                {id: "001",name: "lucy",age: "18"},
                {id: "002",name: "tom",age: "22"},
                {id: "003",name: "lily",age: "16"},
                {id: "004",name: "james",age: "23"},
                {id: "005",name: "marry",age: "17"},
            ],
        };
    },
}
</script>

Info.vue

<template>
    <div>
        <!-- 通过this.$route.query可以获取query中的参数 -->
        编号:{{$route.query.stu.id}}<br/>
        姓名:{{$route.query.stu.name}}<br/>
        年龄:{{$route.query.stu.age}}<br/>
    </div>
</template>
<script>
export default {
    name: "Ab1",
}
</script>

params

可以在路径直接传参,不需要k1=val&k2=val

注意:这种方法,需要在路由配置中声明占位参数

方式一:拼接路径字符串

index.js,声明路由路径的占位参数

export default new VueRouter({
    routes: [
        {
            path: "/home",
            component: Home
        },
        {
            path: "/about",
            component: About,
            children:[
                {
                    // 声明占位参数
                    path: "info/:id/:name/:age",
                    component: Info
                }
            ]
        }
    ]
})

About.vue

<template>
    <div>
        学生信息<br>
        <!-- 使用拼接字符串进行传递参数 -->
        <router-link :to="'/about/info/' + s.id + '/' + s.name + '/' + s.age" v-for="s in stu" :key="s.id">
            {{s.name}}
            <br/>
        </router-link>
        <router-view/>
    </div>
</template>
<script>
export default {
    name: "About",
    data() {
        return {
            stu: [
                {id: "001",name: "lucy",age: "18"},
                {id: "002",name: "tom",age: "22"},
                {id: "003",name: "lily",age: "16"},
                {id: "004",name: "james",age: "23"},
                {id: "005",name: "marry",age: "17"},
            ],
        };
    },
}
</script>

Info.vue

<template>
    <div>
        <!-- 通过this.$route.params可以获取路径中拼接的参数 -->
        编号:{{$route.params.id}}<br/>
        姓名:{{$route.params.name}}<br/>
        年龄:{{$route.params.age}}<br/>
    </div>
</template>
<script>
export default {
    name: "Ab1",
}
</script>

方式二:to的对象写法

注意:此处必须使用命名路由,不可以使用path

index.js,声明命名路由和占位参数

export default new VueRouter({
    routes: [
        {
            path: "/home",
            component: Home
        },
        {
            path: "/about",
            component: About,
            children:[
                {   
                    name: "abInfo",
                    path: "info/:id/:name/:age",
                    component: Info
                }
            ]
        }
    ]
})

About.vue

<template>
    <div>
        学生信息<br>
        <!-- 使用对象进行传递参数 -->
        <router-link :to="{name:'abInfo',params: {id:s.id,name:s.name,age:s.age}}" v-for="s in stu" :key="s.id">
            {{s.name}}
            <br/>
        </router-link>
        <router-view/>
    </div>
</template>
<script>
export default {
    name: "About",
    data() {
        return {
            stu: [
                {id: "001",name: "lucy",age: "18"},
                {id: "002",name: "tom",age: "22"},
                {id: "003",name: "lily",age: "16"},
                {id: "004",name: "james",age: "23"},
                {id: "005",name: "marry",age: "17"},
            ],
        };
    },
}
</script>

Info.vue

<template>
    <div>
        <!-- 通过this.$route.params可以获取路径中拼接的参数 -->
        编号:{{$route.params.id}}<br/>
        姓名:{{$route.params.name}}<br/>
        年龄:{{$route.params.age}}<br/>
    </div>
</template>
<script>
export default {
    name: "Ab1",
}
</script>

props

可以让路由更加简便的接收参数

export default new VueRouter({
    routes: [
        {
            path: "/home",
            component: Home
        },
        {
            path: "/about",
            component: About,
            children:[
                {   
                    name: "abInfo",
                    path: "info/:id/:name/:age",
                    component: Info,
                    //1、对象写法,该对象中的所有属性,可以作为组件Info的data使用
                    //props: {a:1,b:2}
                    //2、布尔写法,如果为ture,就会将路由参数params所有值,以props形式传给Info组件
                    //props: ture
                    //3、函数写法,参数是$route,返回值对象中的所有属性,可以作为组件Info的data使用
                    props(route){
                        return {id:"001",name:"lucy"}
                    }
                }
            ]
        }
    ]
})

编程式路由跳转

除了使用router-link标签实现路由跳转,还可以使用js代码完成跳转。由于所有的组件实例对象都有一个$router属性,就可以通过这个属性来操作跳转。

//push、replace传参和在router-link中to属性值一样
//1、直接传入路径
this.$router.push("/home");
this.$router.replace("/about");
//2、传入对象
this.$router.push({
    path:'/about/info',
    query:{stu:s}
});
this.$router.replace({
    name:'abInfo',
    params: {
        id:s.id,
        name:s.name,
        age:s.age
    }
});

//back可以后退一步
this.$router.back()

//forward可以前进一步
this.$router.forward()

//go可以跳转历史,前进指定的步数
//后退两步
this.$router.go(-2)

避免路由重复控制台报错

在router的index.js中添加,避免路由重复push导致控制台爆红Avoided redundant navigation to current location

const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
    return originalPush.call(this, location).catch(err => err)
}

缓存路由组件

由于组件在切换的时候,老的组件会被销毁,组件中的数据会丢失;但是在实际开发中,例如表单数据,希望在切换组件后,再回来还能保持,那么就需要使用缓存路由组件

<!-- 使用该标签,默认情况下,router-view中展示的所有组件都会被销毁 -->
<!-- 也可以使用include属性,指定需要缓存的组件name -->
<!-- 对于多个组件,可以使用逗号分隔,也可以使用v-bind -->
<keep-alive include="Home">
	<router-view/>
</keep-alive>

路由生命周期

路由组件除了有普通组件的生命周期钩子外,还有自己独有的钩子

activated

路由组件被激活(显示)

deactivated

路由组件失活(切换到其他组件)

路由守卫

注意:在实际开发中,路由守卫进行校验,如果都是用name、path进行校验,那么不容易维护,而且逻辑混乱,这个时候,我们可以例如route的meta属性(路由元信息),添加一个boolean类型的参数,来判断该路由是否需要校验。

全局前置

router/index.js中对router对象进行配置,在路由的切换前进行回调(路径不发生变化),一般用于对路由切换的合法性进行验证

router.beforeEach(function(to,from,next){}),beforeEach接收一个回调函数,这个函数有三个参数

to:想要切换到的目标route对象

from:来源的route对象

next:是一个函数,如果调用的话,说明放行(进行切换)

全局后置

router/index.js中对router对象进行配置,在路由的切换后进行回调(路径已经变化),一般用于组件切换后的渲染,例如title的变化

router.afterEach(function(to,from){}),afterEach接收一个回调函数,这个函数有两个参数

to:想要切换到的目标route对象

from:来源的route对象

独享

在route配置项中进行配置,只对当前的route生效

独享路由守卫只有前置beforeEnter(function(to,from,next){}),参数使用和全局前置一样,没有后置

组件内

直接声明在路由组件的配置项中,注意:beforeRouteEnter中,不能获取组件实例对象this

beforeRouteEnter(to,from,next){},通过路由规则进入当前组件时进行调用

beforeRouteLeave(to,from,next){},通过路由规则离开当前组件时进行调用

hash模式和history模式

注意:如果使用history模式,可能会导致路由刷新404,这是因为浏览器向服务器发送了请求,而服务器没有这个资源,解决方式,例如nginx,添加如下配置

# 解决vue路由模式为history时刷新返回404的问题
location / {
    root /usr/share/nginx/html;
    index /index.html;
    // 找不到文件则尝试直接返回index.html,而vue路由会监控地址栏的俩变化,因此还是在当前页刷新的效果
    try_files $uri $uri/ /index.html; 
}

路由懒加载

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('../views/Home.vue') //进行l
  },
  {
    path: '/about',
    name: 'about',
    component: () => import('../views/About.vue')
  }
]

const router = new VueRouter({
  routes
})

export default router