TOC

前端路由

    路由这个概念最先是运用在后端的,因为⻚⾯的跳转在初期都是通过服务器端进⾏控制的,但是随着SPA(Single Page Application)前端单页应用程序的诞生,前端路由随之而出,传统的⻚⾯的跳转,是通过前端向后台发送请求,后台通过模板引擎的渲染,将⼀个新的HTML界⾯再返回给前台,但是在SPA(即只有⼀个HTML)的出现后,前端可以⾃由控制组件的渲染,来模拟⻚⾯的跳转,实现前端的路由;
    因为对于SPA来讲,整个网站只有一个页面,所以它是无刷新的,URL变化只会带来组件的切换,前端路由就是为了实现这种单页应用而出现的,请求者请求不同的URL地址,会返回不同的组件,可以认为前端路由就是URL和组件之间的一种映射关系,不同的URL对应不同的组件;

前端路由模式

    那么前端路由的核心主要就是在改变URL的时候,页面不进行整体的刷新,只进行相应的组件切换,所以前端路由的核心是,在改变URL时,不进行页面刷新,那么要实现这种技术,主要两种方式,即hash模式和history模式;
hash模式
    hash模式是一种把前端路由的路径用井号(#)拼接在真实URL后面的模式,当井号(#)后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发hashchange事件,一旦该事件触发,我们就可以进行组件的切换,如下示例;
<!doctype html>
<html lang="en" id="">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<a href="#/a">A页面</a>
<a href="#/b">B页面</a>
<div id="app"></div>
<script>
    function render() {
        app.innerHTML = window.location.hash
    }

    window.addEventListener('hashchange', render)
    render()
</script>
</body>
</html>
    在上面的例子中,我们利用a标签设置了两个路由导航,把app当做视图渲染容器,当切换路由的时候触发视图容器的更新,这其实就是大多数前端框架哈希路由的实现原理;
history模式
    history API是H5提供的新特性,允许开发者调用对应的API直接更改前端路由,即更新浏览器URL地址而不重新发起请求,这种模式,就不需要使用类似锚点的这种方式来呈现我们的URL了,显得更加的美观;
    这种模式充分利用history.pushState API来完成URL跳转而无须重新加载页面,同时还有popstate事件来监听state变化,当URL发生改变时,会被该事件捕获到,然后我们就可以在这个事件处理函数中来渲染与之对应的组件,如下示例;
<!doctype html>
<html lang="en" id="">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<a href="javascript:toA();">A页面</a>
<a href="javascript:toB();">B页面</a>
<div id="app"></div>
<script>
    function render() {
        app.innerHTML = window.location.pathname
    }

    function toA() {
        history.pushState({}, null, '/a')
        render()
    }

    function toB() {
        history.pushState({}, null, '/b')
        render()
    }

    window.addEventListener('popstate', render)
</script>
</body>
</html>

Vue Router

    Vue Router是Vue.js官方提供的路由管理器,它和Vue.js的核心深度集成,主要就是为了实现SPA单页面中的路由机制,Vue Router就是基于路由和组件来实现的,将URL和组件建立映射关系,所以在Vue中,URL的转变就等于组件的安装;

安装

    那么因为Vue Router是一个单独的包,所以想要正式使用Vue Router首先需要安装,安装也非常的简单,直接使用npm命令即可,如下;
[cce@doorta /usr/local/Project/front]# npm install vue-router

基本使用

    那么在安装了Vue Router之后,就可以直接将这个包引入到我们的Vue项目当中了,那么想要在Vue中想要使用路由,那么最核心的就是需要维护一个URL到组件的映射关系,所以,我们首先需要创建一个映射关系对象;
    但是因为这个映射表可能会根据项目的复杂程度变得异常的庞大,所以建议将这个映射表独立出来,作为一个单独的模块来设计,那么在设计我们的路由表时,主要需要用到一个函数,第一个函数为createRouter,该函数的主要作用就是来创建路由表的,该函数的参数为一个对象,这个对象内最少给定两个参数;
    第一个参数为routes,它是一个数组,该数组内的元素是一个一个的对象,这个对象内最少有两个属性,第一个属性为path,它的值为URI地址,第二个属性为component,表示该URI与之对应的组件;
    第二个参数为history,它主要是用来指明,我们使用哪一种路由模式的,根据上述的讲解,主要有两种,这两种模式我们都需要通过调用不同的函数来获得,其实这两个函数也都在vue-router这个包中,我们可以直接使用import拿到;
    第一种为hash模式,该模式对应的函数为createWebHashHistory,该函数是使用锚点的方式来定义我们的URI,第二种模式为history,该模式对应的函数为createWebHistory,该函数就是我们常见的URI形式,其实推荐使用history模式,如下示例;
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router" // 导入对应的函数
import home from "./components/home.vue"  // 引入组件
import about from "./components/about.vue"

const router = createRouter({
    routes: [  // 建立URL与组件的对应关系
        {path: "/home", component: home},
        {path: "/about", component: about},
    ],
    history: createWebHistory() // hash模式为createWebHashHistory
})

路由注册

    那么如果我们希望上述的路由与组件的对应关系,能够被Vue项目应用,我们还需要将这个路由表注册到Vue实例当中,那么,对于注册来讲,主要就是调用Vue实例的use方法,该方法是一个函数,它默认接收一个参数,直接将我们的路由对象作为参数传入进去即可;
    但是需要注意的是,注册路由是在Vue实例调用mount方法之前,所以,我们就需要将这个路由暴露出去,然后将路由表注册到当前的Vue项目当中,如下示例;
// router.js
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router" // 导入对应的函数
import home from "./components/home.vue"  // 引入组件
import about from "./components/about.vue"

export default createRouter({
    routes: [  // 建立URL与组件的对应关系
        {path: "/home", component: home},
        {path: "/about", component: about},
    ],
    history: createWebHistory() // hash模式为createWebHashHistory
})

// main.js
import {createApp} from 'vue'
import App from './App.vue'
import router from "./router.js"

const app = createApp(App)
app.use(router)
app.mount('#app')

渲染容器

    至此,我们的路由已经正式被当前项目所应用,但是我们还无法真正的在页面中实现路由切换,因为我们还缺少一个重要的部件,即渲染容器,所谓渲染容器,就是组件最终渲染的地方,在前面就说过,Vue是一个单页面的前端框架,所以它不是一个URL渲染一个页面,它是只有一个页面,然后在这个页面中,去渲染我们的组件,那么这个渲染组件的地方,我们一般称之为渲染容器,它一般只用来做渲染;

router-view元素

    那么有了渲染容器之后,我们就可以开始渲染组件了,但是这里又存在一个问题,因为对于Vue项目来讲,一个页面可能是由多个组件组成的,比如,Header一个组件,Context一个组件,Footer是一个组件,整个页面由这三个组件组成;
    那么同样的,也是因为Vue是一个单页面应用,所以说,不管怎么渲染,都是在一个页面中渲染,那么当这个页面存在多个组件时,具体是在哪里渲染呢,是在Context中渲染,还是Footer中渲染,所以此时,我们需要一个占位符,将这个占位符放在具体需要渲染的位置,从而实现局部组件渲染,那么这个占位符,在Vue中,就是一个元素,即<router-view>元素;
    当然,如果一个页面只有一个组件,那么我们就只需要在渲染容器中定义一个占位符即可,如下示例;
<!-- App.vue -->
<template>
  <div>
    <p><a href="/home">Home</a></p>
    <p><a href="/about">About</a></p>
    <router-view></router-view>  <!-- 渲染占位符 -->
  </div>
</template>

router-link元素

    可以看到,上面我们通过<a>元素实现点击跳转URL,那么在Vue框架中,其实提供了一种更加优雅的实现方式,同样的,它也是一个元素,即<router-link>,该元素必须有一个to属性,这个to属性就和<a>元素的href属性是一样的,表示该元素发生点击事件后,将要跳转的地址,如下示例;
<!-- App.vue -->
<template>
  <div>
    <p><router-link to="/home">Home</router-link></p>
    <p><router-link to="/about">About</router-link></p>
    <router-view></router-view>
  </div>
</template>

  • 提示:其实<router-link>元素最终还是被渲染成了<a>元素,本质上和直接使用<a>元素没什么区别,只不过使用<router-link>这种方式,更加的符合Vue的编码方式;
replace属性
    <router-link>元素还有一个replace属性,该属性的值为replace,那么当<router-link>元素添加上replace属性时,当页面跳转之后,就无法回到上一个页面;
active-class属性
    其实如果仔细的分析<router-link>元素,会发现,当我们点击了一个<router-link>元素之后,默认会在这个被点击的<router-link>元素上面添加一个router-link-active的class属性,那么如果我们希望自定义这个class值时,可以使用active-class属性来实现,如下示例;
<template>
  <div>
    <p><router-link to="/home" active-class="active">Home</router-link></p>
    <p><router-link to="/about" active-class="active">About</router-link></p>
    <router-view></router-view>
  </div>
</template>

<style>
.active {
  color: red;
}
</style>

redirect属性

    如果我们希望在用户访问一个特定的URL时,自动跳转到另一个URL,如果当访问"/"路径时,默认跳转到"/home"路径,那么想要实现这个功能,就需要借助路由跳转来实现,路由跳转就时在定义路由时,将component属性更改为redirect即可,且值为需要跳转的目的路径,如下示例;
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router"
import home from "./components/home.vue"
import about from "./components/about.vue"

export default createRouter({
    routes: [
        {path: "/", redirect: "/home"}, // 建立跳转关系
        {path: "/home", component: home},
        {path: "/about", component: about},
    ],
    history: createWebHistory() // hash模式为createWebHashHistory
})

name属性

    在routes路由映射表的数组对象内,有一个name属性,该name属性主要是作用就是对当前对路由创建一个别名,这个别名一般可以在三个地方拿到,第一,能够在路由守卫函数中拿到,第二,我们可以在模版中使用Mustache语法拿到,第三,我们可以可以直接在,Setup函数中拿到,如果我们使用Options API,可以直接在methods方法函数中使用this.$route拿到,如下示例;
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router"
import home from "./components/home.vue"
import about from "./components/about.vue"

const router = createRouter({
    routes: [
        {path: "/", redirect: "/home"},
        {path: "/home", component: home, name: "HomePage"}, // 指定name属性
        {path: "/about", component: about, name: "AboutPage"},
    ],
    history: createWebHistory()
})
export default router
模版获取
    如果我们希望在模版中获取到路由对象的name属性,可以直接使用Mustache语法,但是这里需要注意的是,name属性在"$route"对象内,如下示例;
<h1>{{ $route.name }}</h1>
Setup函数获取
    在Setup函数获取比较麻烦,在Vue 2版本中,我们可以直接在methods对象内的方法使用this.$route拿到,但是在Vue 3的Setup函数中,没有this,所以我们无法这样使用,那么vue-router给我们提供了另一种方式,vue-router模块中,有一个全局函数,这个全局函数实现的功能就是在Vue 2中的this.$route,所以我们可以通过这个全局函数的返回值,去获取到name属性,如下示例;
import {useRouter} from "vue-router" // 因为在Setup函数中没有this,所以无法使用Vue2中的this.$router,因此引入一个useRouter函数,拿到这个在Vue2中的this.$router对象

const router = useRouter() // 当前路由对象
console.log(router.name) // 获取当前组件的路由别名

mate属性

    如果我们希望增加一些自定义属性到路由对象中,可以直接在routes路由表的数组对象内添加一个meta属性,该属性是一个对象,我们可以将自定义数据直接放在这个对象内,然后这个对象将直接绑定在路由对象当中,可以直接使用router对象的meta属性获取,同时,它和name属性一样,可以在模版中、Setup函数或者路由守卫中使用路由对象获取,如下示例;
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router"
import home from "./components/home.vue"

const router = createRouter({
    routes: [
        {path: "/", redirect: "/home"}, // 建立跳转关系
        {path: "/home", component: home, name: "HomePage", meta: {name: "cce", age: 18}}, // 指定meta属性
    ],
    history: createWebHistory()
})
export default router

路由传参

    所谓路由传参,就是根据URL来向组件传递参数,在Vue-router中,如果希望实现路由传参,可以直接从URL获取某个字符串并作为参数将这个字符串传递给组件,从而实现动态路由参数,那么想要给我们的组件动态传递参数就主要有两种方式了,第一种为params,第二种为query;
params
    params这种传递参数的方式,主要是在路由表层面进行配置,HTTP的URL路径一般是以"/"分割的,如果我们希望将某个参数传递给组件可以使用":params_name"的形式向组件传递参数;
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router"
import user from "./components/user.vue"

const router = createRouter({
    routes: [
        {path: "/user/:user_id", component: user}, // 配置URL参数
    ],
    history: createWebHistory()
})
export default router
    以上述的路由为例,当配置完成我们的路由之后,用户就需要使用/user/${id}的方式来访问我们的组件,这样,Vue框架,会自动将${id}位置的字符串传递给组件,然后就可以在组件中就可以像上述的name一样,在模版中、Setup函数或者路由守卫中获取到这个参数,该参数将在router对象下面的params属性里面,以一个对象的形式存储,如下示例;
<!--父组件-->
<template>
  <div>
    <p><router-link to="/user/1">User</router-link></p>
    <router-view></router-view>
  </div>
</template>

<!--子组件-->
<template>
  <div>
    <p>{{ router.params }}</p>
  </div>
</template>

<script setup>
import {useRouter} from "vue-router"
const router = useRouter()
</script>

query
    query这种方式给组件传递数据无需修改我们的路由配置,只需要我们在访问该组件的时候,加入query查询参数即可,查询参数以"?"问号开头,参数呈现键值对儿形式,多个键值对儿之间使用"&"符号进行分割;
    那么,想要这个query参数,就直接使用router对象的query属性即可拿到,如下示例;
<!--父组件-->
<template>
  <div>
    <p><router-link to="/user/1?name=cce">User</router-link></p>
    <router-view></router-view>
  </div>
</template>

<!--子组件-->
<template>
  <div>
    <p>{{ router.query }}</p>
  </div>
</template>

<script setup>
import {useRouter} from "vue-router"
const router = useRouter()
</script>

错误页

    如果用户访问一个不存在的URL,此时我们的页面不会有任何变化,没有任何提示,可能会误导用户,所以在这种情况下,我们可以给这种类型的请求,返回一个错误页面,友好的提示用户当前访问的页面不存在;
    那么想要实现错误页其实也很简单,我们只需要在routes路由表的数组对象内的path属性做一些特殊的设置即可,将错误页面的path属性设置为"/:pathMatch(.*)"固定语法即可;
import {createRouter, createWebHistory} from "vue-router"
import notfound from "./components/notfound.vue"

const router = createRouter({
    routes: [
        {path: "/:pathMatch(.*)", component: notfound}
    ],
    history: createWebHistory()
})
export default router
    同时,我们如果希望拿到这个错误页面的URL,也可以使用router路由对象的params属性拿到,如下示例;
<!--子组件-->
<template>
  <div>
    <h4>router对象的params属性值为:{{ $route.params }}</h4>
    <p style="color: red">Page {{ $route.params.pathMatch }} Not Found</p>
  </div>
</template>

编程式导航

    在前期,我们希望在一个组件中跳转到另一个组件,都是使用<router-link>元素来实现的,那么如果我们希望通过自定义事件来进行路由跳转呢,或者当用户请求错误,跳转到错误页之后,我们希望在2秒中后自动跳转到首页呢,使用<router-link>的方式是无法实现的;
    那么这个时候,我们就需要借助编程式导航来实现,所谓编程式导航,就是通过JavaScript代码来实现页面的跳转,在router对象中,其实还有一个方法函数,即push,它可以实现类似<router-link>一样的需求,进行页面跳转;
    该方法函数,接收一个参数,该参数可以是一个URL路径,也可以是一个对象,如果是一个对象,这个对象内必须具备path属性,当然,我们也可以为其指定params或者query等属性,在跳转时附带一些参数,如下示例;
<!--子组件-->
<template>
  <div>
    <p style="color: red">您访问的页面不存在,页面将在2秒后跳转到首页</p>
  </div>
</template>

<script setup>
import {useRouter} from "vue-router"

const router = useRouter() // 拿到router对象
setTimeout(function () {
  router.push({path: "/home", query: {name: "cce"}})
}, 2000)
</script>

动态路由

    一般情况下,我们都会将路由的映射表在项目上线前配置好,清晰的指明哪一个URL对应哪一个组件,但是在实际的应用场景中,其实并非这样,在某些特殊的应用中,路由映射的配置可能并非在预先就配置好的,比如常见的后台管理系统,所谓后台管理系统就是对项目的一些数据进行综合管理的一个平台,那么既然是管理这种敏感的数据,所以,必不可少的,我们需要加入权限的元素,实现数据的严格管控;
    那么既然加入了权限的元素,就有问题了,权限分很多种,比如超级管理员、管理员、普通操作员等等,操作员可以管理一些简单的数据,管理员可以管理大部分数据,而超级管理员可以管理全部的数据,也就是说,不同的角色要实现不同的权限管控,我们不可能对每一条数据进行严格的管控,但是我们可以对路由系统进行管控,比如,不同角色的用户,只可以访问不同的URL,那么这就有一个要求,在这种场景下,路由必须动态的注册,不能将其直接在路由对象中写死;
    那么想要动态的写入路由,Vue-Router也给我们提供了两个好用的方法函数,在router对象中两个addRoute方法函数,第一个addRoute方法函数,主要是用来注册一级路由的,接收一个参数,这个参数是一个对象,这个对象内就是路由的映射关系配置,第二个addRoute方法函数,主要是用来新增二级路由的,第一个参数为父路由的name名称,第二个参数为路由的映射关系配置,但是这个路由的映射关系配置有点我们需要注意,它的path属性,不可以"/"开头;
import {createRouter, createWebHistory} from "vue-router"
import notfound from "./components/notfound.vue"
import user from "./components/user.vue"

const router = createRouter({
    routes: [
        {path: "/:pathMatch(.*)", component: notfound},  // 仅加入一个404页面的路由
    ],
    history: createWebHistory()
})

// 动态添加一级路由,访问路径为 /user
router.addRoute({path: "/user", component: user, name: "user"})
// 动态添加二级路由,访问路径为 /user/id
router.addRoute("user", {path: "id", component: user, name: "user_info"})
// 动态添加三级路由,访问路径为 /user/id/1
router.addRoute("user_info", {path: "1", component: user, name: "user_info_id"})

router.getRoutes().forEach((item) => {
    console.log(item)
})
export default router

  • 注意:如果使用addRouter添加的路由重复了,那么会进行一个覆盖的操作;
删除路由
    删除路由,如果我们希望动态删除路由,也可以直接调用router对象的removeRoute的方法,去动态的删除路由,该方法接收一个参数,即路由的name值。所以建议,给每一条映射关系都给定一个name属性,可能在不经意间,我们就会用到;

路由守卫

    路由守卫是路由在跳转前、后过程中的一些钩子函数,这些函数可以做一些特定的操作,来辅助路由跳转的这个操作,比如,在一个后台管理平台中,在实现路由跳转前一般都会校验是否有要跳转的URL的访问权限,有权限就可以通过,反之就会被执行其他操作,如返回首页;
    在Vue-Router当中,路由当行守卫有三种类型,全局导航守卫、路由独享守卫、组件内守卫,目前我们主要理解全局导航守卫,所以全局导航守卫,就是当URL路径改变的时,守卫的钩子函数,这样常用的钩子函数,主要有两个,即,beforeEach和afterEach,beforeEach是在URL跳转之前执行的一个钩子函数,而afterEach则是在URL跳转之后执行的钩子函数;
beforeEach
    beforeEach钩子函数,它主要是在导航触发的那一刻,正式跳转之前执行的一个钩子函数,Vue-Router框架在回调这个函数时,默认会传递三个参数,第一个参数为一个对象,表示此次导航事件来自哪个页面,第二个参数,同样是一个对象,表示此次导航需要跳转到哪个映射对象;
    第三个参数为一个函数,它是来决定如何进行跳转的,如果我们给他传递一个false参数,那么将会取消此次导航,如果我们给他传递一个包含path属性的对象或者一个URL,那么会直接跳转到这个URL,next函数在Vue 2.x中大量使用,在Vue 3中,推荐使用return返回值实现;
    beforeEach的返回值也是有讲究的,如果返回false表示取消此次导航,如果返回undefined或者不提供return关键字,会继续此次导航,同样的,如果返回的是一个URL,那么会直接跳转到这个返回的URL,也可以返回一个包含path、query、params等信息的对象,如下示例;
import {createRouter, createWebHistory} from "vue-router"
import notfound from "./components/notfound.vue"
import home from "./components/home.vue"
import user from "./components/user.vue"

const router = createRouter({
    routes: [
        {path: "/:pathMatch(.*)", component: notfound, meta: {router_id: 100000}},
        {path: "/home", component: home, name: 'home', meta: {router_id: 110000}}
    ],
    history: createWebHistory()
})

router.beforeEach((to, from) => {
    if (to.path == '/') {
        return {path: "/home"}
    }
})

export default router
  • 注意:beforeEach的第一个参数是一个函数对象,如果我们希望使用return语句来实现页面跳转,就不要在该函数内接收第三个参数,即next函数,如果我们希望使用next函数来实现,那么在内部就不要使用return语句;

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注