# Vue3教程 - 18 路由

什么是路由?

路由是构建单页面应用(SPA)的重要组成部分,使用路由可以通过不同的URL路径来访问不同的页面组件,而无需重新加载整个页面。

也就是说路由可以根据不同的URL实现页面组件的切换,不需要重新加载整个页面。或者点击菜单,只切换内容区的内容,不是整个页面的刷新。

# 18.1 vue-router

Vue 中的路由是由 vue-router 库提供的,它是一个为 Vue.js 应用设计的官方路由管理器,它和Vue.js的核心深度集成。

也就是说在 Vue 中时候用 Vue Router 实现路由管理。

# 1 vue-router安装

我们在使用 vite 创建项目的时候,如果勾选了 vue-router,那么在创建的项目中是集成了 vue-router 的。在项目 src 下会生成router 文件夹,在文件夹下会有 index.ts ,其中配置了初始项目的路由配置:

router/index.ts

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('../views/AboutView.vue')
    }
  ]
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在上面代码中创建了 VueRouter 对象,在 routes 中定义了各个页面的路由。其实从上面也可以看出来,路由就是将 path 指向对应的页面组件

定义了路由然后在项目的 main.js 中引入路由配置:

import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'
// 引入路由
import router from './router'

const app = createApp(App)
// 使用路由
app.use(router)

app.mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12

如果在创建项目的时候没有勾选安装 vue-router,那么需要用 npmcnpm 来进行安装的。打开命令行工具,进入你的项目目录,输入下面命令。

# npm安装
npm install vue-router

# yarn安装
yarn add vue-router
1
2
3
4
5

然后在 src 目录下新建 router 文件夹,然后在其下新建 index.ts,并编辑内容如下:

// 引入createRouter和createWebHistory
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  // 配置路由模式为history
  history: createWebHistory(),
  routes: [
    // ...后面在这里配置路由
  ]
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12

在上面的代码中创建 VueRouter 实例并导出,后面在 routes 中配置各个路由就可以了。

然后在项目的 main.ts 中导入路由对象并使用。

main.ts

import { createApp } from 'vue'
import App from './App.vue'
// 引入路由
import router from './router'

const app = createApp(App)
// 使用路由
app.use(router)

app.mount('#app')
1
2
3
4
5
6
7
8
9
10

# 18.2 使用vue-router

下面来学习如何使用 Vue Router,通过两个按钮实现两个组件页面的切换。

登录首页之间进行切换。

# 1 创建页面

首先创建两个页面,其实页面也是组件,我们将页面放在 src/pages 目录下。

一般将页面放在 src/pagessrc/views目录下,将组件放在 src/components 目录下。

HomePage.vue

<template>
    <div>
        主页页面
    </div>
</template>

<!-- setup -->
<script lang="ts" setup>

</script>
1
2
3
4
5
6
7
8
9
10

LoginPage.vue

<template>
    <div>
        登录页面
    </div>
</template>

<!-- setup -->
<script lang="ts" setup>

</script>
1
2
3
4
5
6
7
8
9
10

# 2 配置路由

router/index.js 中导入页面组件,并进行路由配置:

import { createRouter, createWebHistory } from 'vue-router'
import HomePage from '@/pages/HomePage.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    // 配置路由
    {
      path: '/home',
      name: 'home',
      component: HomePage
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('@/pages/LoginPage.vue') // 这样表示延迟加载
    }
  ]
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在上面添加了两个页面的路由,path 表示访问页面的路由,name 是可选的,但是后面在代码中编程的时候,可以通过名称来跳转和控制。 component 表示的是页面组件,上面使用了不同的方式,LoginPage.vue的引入使用了动态导入(即懒加载)的方式,这有助于减少应用的初始加载时间。component: () => import("@/pages/LoginPage.vue"), 这行代码意味着当路由到 /login时,LoginPage.vue 组件会被动态地加载。

# 3 使用路由

下面在 App.vue 组件中使用路由。

<template>
  <div>
 
    <RouterLink to="/home">首页</RouterLink>&nbsp;
		<RouterLink to="/login">登录</RouterLink>

    <!-- 页面在这里展示 -->
    <RouterView></RouterView>
  </div>
</template>

<!-- setup -->
<script lang="ts" setup>
// 引入RouterView 和 RouterLink
import { RouterView, RouterLink } from 'vue-router';

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

首先引入 RouterViewRouterLink 。 然后使用 <RouterLink> 配置了两个链接,分别跳转到 首页登录 页面。

<RouterView> 相当于占位符,路由匹配到的组件,会显示在 <RouterView> 中。

当点击首页和关于,会跳转到指定的页面,并且地址栏也是发生变化:

# 18.3 vue-router使用细节

# 1 RouterLink标签

<RouterLink> 有一个 replace 属性,这个表示点击链接后跳转到指定的页面后,前面那个页面的历史记录会被覆盖掉,无法通过浏览器的 后退 功能返回到上一个页面。浏览器的历史记录就像一个栈。

<RouterLink replace to="/home">首页</RouterLink>
1

# 2 路由redirect重定向的使用

在上面实现的功能,点击首页按钮显示首页组件,点击登录按钮显示登录组件。但是当第一次进入到页面的时候,访问的是根路径,没有匹配到路由,则不会显示任何组件。

当然我们可以在路由中配置一个根路由,指向首页:

{ path: '/', component: HomePage }
1

但是这样会多个路径跳转到首页,很不统一。所以我们可以在匹配到根路径的时候,重定向到首页。

// 2.配置页面路由
const routes = [
    {
        path: '/',
        redirect: '/home'  // 重定向到首页
    },
    {
        path: "/home",
        name: "home",
        component: HomePage,
    },
    {
        path: "/login",
        name: "login",
        component: () => import("@/pages/LoginPage.vue"),
    },
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

可以在路由配置中,添加 / 的路由,使用 redirect 属性,重定向到 /home

# 3 选中的路由的高亮显示

在上面实现的功能里,如果在点击 首页 链接的时候,首页 链接高亮显示,点击 登录 链接, 登录 链接高亮显示,那么该如何实现呢?

通过使用浏览器开发者工具,审查页面元素可以发现,点击 首页 链接以后,首页链接会添加 .router-link-activecss 样式类,点击 登录 链接的时候,登录链接会添加 .router-link-active 的 css 样式类,那么我们可以定义一个 .router-link-active 样式类,在其中定义样式即可。

例如:

<style scoped>
.router-link-active {
  color: red;
  font-weight: 800;
  text-decoration: underline;
  background-color: greenyellow;
}
</style>
1
2
3
4
5
6
7
8

这样就可以实现选中的路由高亮的功能了。显示如下:

其实 .router-link-active 的名称是可以自定义的。我们在创建 VueRouter 对象的时候,可以使用一个 linkActiveClass 属性来自定义这个名称。

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  linkActiveClass: 'my-active',  // 指定RouterLink激活的样式
  routes: [
    // ...配置路由
  ],
});
1
2
3
4
5
6
7

我们指定了样式名称为 my-active,然后在定义样式的时候,定义样式名称为 .my-active 的样式就可以了,取代默认的 .router-link-active,这样可以实现使用第三方的样式了。


还有一种方式就是直接在 <RouterLink> 标签上添加 active-class="active" ,这样哪个标签被选中就会添加 active CSS 样式。

所以在编写active 样式即可,如下:

<template>
  <div>
 
		<!-- 1.添加active-class="active" -->
    <RouterLink to="/home" active-class="active">首页</RouterLink>&nbsp;
		<RouterLink to="/login" active-class="active">登录</RouterLink>

    <RouterView></RouterView>
  </div>
</template>

<!-- setup -->
<script lang="ts" setup>
import { RouterView, RouterLink } from 'vue-router';

</script>

<style scoped>
/** 2.编写.active样式 */
.active {
  color: red;
  font-weight: 800;
  text-decoration: underline;
  background-color: greenyellow;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

实现效果和上面的一样。

# 4 组件切换动画

实现动画很简单,和之前组件的切换动画是一样的:

  1. <RouterView> 标签使用 <transition> 标签包裹
<transition mode="out-in">
    <RouterView></RouterView>
</transition>
1
2
3
  1. 编写动画的样式
<style scoped>
.v-enter,
.v-leave-to {
    opacity: 0;
    transform: translateX(140px);
}

.v-enter-active,
.v-leave-active {
    transition: all 0.5s ease;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12

# 18.4 路由传参

# 1 使用query方式传递参数

传递参数

直接在点击的链接上拼接字符串即可:

<RouterLink to="/home?id=10&name=doubi">首页</RouterLink>
1

如果要动态传递参数,参数来自 setup ,可以这样写:

<RouterLink :to="`/home?id=${id}&name=${name}`">首页</RouterLink>
1

也可以使用对象的写法,给 :to 传递一个对象(推荐这种写法):

<RouterLink :to="{
    path: '/home',
    query: {
        id: 13,
        name: 'doubi'
    }
}">首页</RouterLink>

<!-- 使用name也可以 -->
<RouterLink :to="{
    name: 'name',  // name就是定义路由时候的名称
    query: {
        id: 13,
        name: 'doubi'
    }
}">首页</RouterLink>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

获取参数

在组件中,通过 useRoute() 获取 route ,然后通过 route.query 对象获取传递的参数。

<template>
    <div class="home">
        主页页面
    </div>
    <div>参数: {{ route.query.id }} --- {{ route.query.name }}</div>
</template>

<!-- setup -->
<script lang="ts" setup>
// 通过hooks的方式引入
import { useRoute } from 'vue-router';

let route = useRoute();
console.log('id:', route.query.id);
console.log('name:', route.query.name);

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

**注意:使用的是 useRoute(),后面还会有 useRouter(),注意区分。 **

上面使用 route.query.idroute.query.name 很长,有点麻烦,可以直接从 route 中解构出 query ,但是需要注意,从 reactive 解构的时候,结果出的属性会失去响应式,需要使用 toRefs (前面有讲过)。

演示一下:

<template>
    <div class="home">
        主页页面
    </div>
    <div>参数: {{ query.id }} --- {{ query.name }}</div>
</template>

<!-- setup -->
<script lang="ts" setup>
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';

let route = useRoute();
// 转换为响应式
let { query } = toRefs(route);

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2 使用params方式传递路由参数

修改路由规则

修改 home 路由的匹配规则,使用占位符的方式,后面在传递参数的时候,home 后面的内容就会匹配成 id 的参数。

// 2.配置页面路由
{
    path: '/',
    redirect: '/home'
},
{
    path: "/home/:id",  // 使用占位符
    name: "home",
    component: HomePage,
},
{
    path: "/login",
    name: "login",
    component: () => import("@/pages/LoginPage.vue"),
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

如果想传递多个参数,可以在后面继续拼接,但是需要注意下面的 /home/:id/:name 是不会匹配路径 /home/13,因为后面没有name 的参数。只能匹配到类似 /home/13/doubi 的路径:

{
    path: "/home/:id/:name",
    name: "home",
    component: HomePage,
},
1
2
3
4
5

传递参数

/home/13/doubi 表示传递了 id13namedoubi

<RouterLink to="/home/13/doubi">首页</RouterLink>
1

如果要动态传递参数,参数来自组件中的 setup ,可以这样写:

<RouterLink :to="`/home/${id}/${name}`">首页</RouterLink>
1

也可以使用对象的写法,给 :to 传递一个对象:

<RouterLink :to="{
    name: 'home',
    params: {
        id: 13,
        name: 'doubi'
    }
}">首页</RouterLink>
1
2
3
4
5
6
7

注意:上面使用对象的写法,对于params的传参方式,只能用 name 不能用 path 。另外参数不能传递数组。


获取参数

在组件中,通过 useRoute() 获取 route ,然后通过 route.params 对象获取传递的参数。

<template>
    <div class="home">
        主页页面
    </div>
    <div>参数: {{ route.params.id }} --- {{ route.params.name }}</div>
</template>

<!-- setup -->
<script lang="ts" setup>
import { toRefs } from 'vue';
// 通过hooks的方式引入
import { useRoute } from 'vue-router';

let route = useRoute();

// 或者解构
// let { params } = toRefs(route);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3 路由的props属性

在上面使用 query 和 params 传递参数的时候,都需要从 useRoute() 中获取参数,不是很方便,我们可以使用路由的 props 属性来定义如何将路由参数传递给组件。这样做的好处是可以使组件更加解耦,因为它不需要直接从useRoute()中获取参数。

使用的时候,主要有以下几种方式:

  1. 布尔模式;
  2. 函数模式;
  3. 对象模式;

举个栗子:

布尔模式:

props 被设置为true时,route.params 将被设置为组件的 props

路由配置:

{
    path: "/home/:id/:name",
    name: "home",
    component: HomePage,
    props: true
}
1
2
3
4
5
6

组件中获取参数:

<template>
    <div class="home">
        主页页面
    </div>
    <div>参数: {{ id }} --- {{ name }}</div>
</template>

<!-- setup -->
<script lang="ts" setup>
  
// 获取参数
defineProps(['id', 'name']);

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意:这种方式只能将 params 参数作为 props 传递的参数。

函数模式

props 提供一个函数,该函数接收当前路由对象作为参数,并返回一个对象,这个对象将被用作组件的props。这提供了最大的灵活性,允许你根据路由信息动态地决定传递给组件的props

{
    path: "/home",
    name: "home",
    component: HomePage,
    props(route) {
        // 获取 query 参数
        return route.query;
        // 可以获取 params 参数
        // return route.params;
      },
},
1
2
3
4
5
6
7
8
9
10
11

接收参数的和上面的一样,使用 defineProps 方式接收参数。

对象模式

可以在路由配置中配置对象类型的数据,然后作为 props 传递给组件。

路由配置:

通过 props 配置对象类型的数据 。

{
    path: "/home",
    name: "home",
    component: HomePage,
    props: { id: '13', name: 'doubi' }
}
1
2
3
4
5
6

接收参数的和上面的一样,使用 defineProps 方式接收参数。这些数据是静态数据,所以这种方式使用的比较少。

# 18.5 vue-router的跳转

页面跳转

在网页中,有两种页面跳转的方式:

  1. 使用 <a href=""> 标签和 <RouterLink to=""> 标签的形式都叫标签跳转;上面 <RouterLink> 中,直接写 /home 就可以了,如果使用 <a href=""> ,且使用 hash 路由模式(后面路由模式再讲解),需要需要使用 <a href="#/home">

  2. 通过监听元素点击事件,通过 onclick 事件处理,然后使用 window.location.href 的形式,叫做编程式导航vue-router 提供了编程式导航方式,可以通过代码实现导航的跳转:

下面来介绍如何使用代码进行路由跳转,因为正常在开发的时候,点击登录按钮,请求服务器登录,成功后,使用代码跳转到首页。

# 1 通过字符串指定路径

举个栗子:

通过点击按钮,在函数中使用代码跳转到首页和登录页面。

<template>
  <div>
    <button @click="goHome">首页</button>
    <button @click="goLogin">登录</button>

    <!-- 页面在这里展示 -->
    <RouterView></RouterView>
  </div>
</template>

<!-- setup -->
<script lang="ts" setup>
import { RouterView, useRouter } from 'vue-router';

const router = useRouter();

function goHome() {
  router.push('/home');
}

function goLogin() {
  router.push('/login');
}

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

上面通过 router.push('/home'); 跳转到首页,通过 router.push('/login'); 跳转到登录。

注意:引入的是 useRouter 不是 useRoute。

还可以使用 replace

router.replace('/home');
1

# 2 通过对象指定路径

router.push({ path: '/home' })
1

这里的将 path 放在一个对象中。这种方式也可以携带参数。

通过 query 方式传递参数:

router.push({ path: '/home', query: { id: 13, name: 'doubi' }})
// 或者下面这种,但是推荐上面这种
router.push({ path: '/home?id=13&name=doubi'})
1
2
3

params 方式直接写在路径中:

router.push({ path: '/home/13/doubi' })
1

# 3 命名的路由

通过路由名称来跳转:

router.push({ name: 'home'})
1

上面的 name 就是路由中的配置的 name

使用这种方式有一个比较方便的点是,如果是多级路由,只需要写名字就可以了。

{
    path: "/home/message",
    name: "message",  // 这里的name
    component: MessagePage,
},
1
2
3
4
5

直接写名字:

router.push({ name: 'message'})
1

# 4 页面后退

如果想实现页面的后退,使用 router 实现如下:

router.go(-1); 
1

-1 表示后退一步,-2 则表示后退两步。

# 5 页面前进

如果想实现页面的前进,使用 router 实现如下:

router.go(1);
1

# 18.6 路由嵌套

一般在开发中会涉及到组件的嵌套,组件内部存在组件的切换,就会涉及路由的嵌套。

举个栗子:

在 App.vue 中会有一个 <RouterView></RouterView> , 用于登录页面 LoginPage.vue 和首页 HomePage.vue 页面的切换,而首页也会有组件的切换,点击菜单,切换局部的内容的部分,所以需要在 HomePage.vue 中也需要使用 <RouterView></RouterView> ,所以就涉及到 App.vueHomePage.vue 中都使用 <RouterView></RouterView> 的路由嵌套。

下面我们来实现这个功能:

  • 通过 /login 路由跳转到 LoginPage 组件, 在 LoginPage 组件中有一个登录按钮,点击登录按钮跳转到 HomePage.vue 页面;
  • HomePage.vue 页面有三个按钮:退出消息设置, 点击退出按钮,跳转到 LoginPage 组件,点击消息按钮, HomePage.vue 页面局部切换显示消息页面,点击 设置 按钮,HomePage.vue 页面局部切换显示设置页面。

# 1 组件定义

App.vue

页面有一个 <RouterView/> 用于登录页面 LoginPage.vue 和首页 HomePage.vue 页面的切换。

<template>
  <div id="app">
    <RouterView />
  </div>
</template>

<!-- setup -->
<script lang="ts" setup>

</script>
1
2
3
4
5
6
7
8
9
10

LoginPage.vue

登录页面有一个登录按钮,点击登录按钮,通过代码实现路由跳转,跳转到 /home

<template>
    <div class="login">
        <div>登录</div>

        <div>
            <button @click="doLogin">登录</button>
        </div>
    </div>
</template>

<!-- setup -->
<script lang="ts" setup>
import { useRouter } from 'vue-router';
let router = useRouter();

// 跳转到首页
function doLogin() {
    router.push({ path: '/home' })
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

HomePage.vue

首页有三个按钮,退出消息设置,点击按钮,跳转到不同的页面。页面中也有一个 <RouterView></RouterView> 用于消息页面和设置组件的切换。

注意:消息设置页面是在首页进行切换的,所以前面需要加 /home ,属于 /home 的子路由。

<template>
    <div class="home">
        <div>首页</div>
        <div>
            <button @click="logout">退出</button>
            <button @click="message">消息</button>
            <button @click="setting">设置</button>
        </div>

        <RouterView></RouterView>
    </div>
</template>

<!-- setup -->
<script lang="ts" setup>
import { useRouter } from 'vue-router';
let router = useRouter();

// 跳转到登录页面
function logout() {
    router.push({ path: '/login' })
}

// 跳转到消息页面
function message() {
    router.push({ path: '/home/message' })
}

// 跳转到设置页面
function setting() {
    router.push({ path: '/home/setting' })
}

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

MessagePage.vue

<template>
    <div class="login">
        <div>消息</div>
    </div>
</template>

<!-- setup -->
<script lang="ts" setup>

</script>
1
2
3
4
5
6
7
8
9
10

SettingPage.vue

<template>
    <div class="login">
        <div>设置</div>
    </div>
</template>

<!-- setup -->
<script lang="ts" setup>

</script>
1
2
3
4
5
6
7
8
9
10

# 2 路由配置

import { createRouter, createWebHistory } from "vue-router";
import HomePage from "@/pages/HomePage.vue";

const router = createRouter({
  history: createWebHistory(),
  linkActiveClass: "my-active",
  routes: [
    // 配置路由
    {
      path: "/home",
      name: "home",
      component: HomePage,
      children: [ // 子路由配置
        // 子页面配置,这里使用的是懒加载,也可以直接写组件
        { path: "message", component: () => import("@/pages/MessagePage.vue") },
        { path: "setting", component: () => import("@/pages/SettingPage.vue") },
      ],
    },
    {
      path: "/login",
      name: "login",
      component: () => import("@/pages/LoginPage.vue"),
    },
  ],
});

export default router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

在配置路由的时候需要注意:

  • MessagePage.vueSettingPage.vue 是在 HomePage 页面的 <RouterView></RouterView> 中切换的,属于 HomePage 组件的子组件,所以路由需要配置在 path: "/home"children 中。

  • 子路由的路径前面不要带 / ,否则会从根路径来匹配。

运行项目,访问 http://127.0.0.1:5173/login ,然后点击登录,跳转到 ome页面,然后点击设置按钮,显示效果:

# 18.7 缓存路由组件

<KeepAlive> 是一个内置的抽象组件,它主要用于保持组件状态或避免重新渲染。当我们在 Vue 应用中进行路由跳转时,默认情况下,离开的组件会被销毁,再次进入时需要重新渲染。

这样在某些情况下会出问题,例如在一个组件输入了内容还没有保存就切换到另一个组件了,再切换回来导致输入的内容消失了。所以我们可以使用 <KeepAlive> 保留组件的状态或避免重新渲染。

# 1 KeepAlive

举个栗子:

编写 HomePage.vue 然后在其中进行 MessagePage.vueSettingPage.vue 的切换:

<template>
    <div class="home">
        <div>首页</div>
        <div>
            <RouterLink to="/home/message">消息</RouterLink>
            <RouterLink to="/home/setting">设置</RouterLink>
        </div>

        <!-- 使用 v-slot 结合 KeepAlive -->
        <RouterView v-slot="{ Component }">
            <KeepAlive>
                <component :is="Component" />
            </KeepAlive>
        </RouterView>
    </div>
</template>

<script setup lang="ts">

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 通过 v-slot 来解构出当前路由对应的组件 Component ,然后使用 <component> 渲染当前激活的路由组件。

MessagePage.vue

<template>
    <div>
        <div>消息</div>
        <input type="text" />
    </div>
</template>

<!-- setup -->
<script lang="ts" setup>

import { onUnmounted } from 'vue';

// 卸载
onUnmounted(() => {
    console.log("Message已卸载");
});

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

SettingPage.vue

<template>
    <div>
        <div>设置</div>
    </div>
</template>

<!-- setup -->
<script lang="ts" setup>

import { onUnmounted } from 'vue';

// 卸载
onUnmounted(() => {
    console.log("Setting已卸载");
});

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

这样在切换 MessagePage.vueSettingPage.vue 的时候, MessagePage.vue 页面输入的内容并不会丢失。

上面使用 <KeepAlive> 包裹,那么 MessagePage.vueSettingPage.vue 都不会销毁。

如果只想 MessagePage.vue 在切换的时候不会销毁,还可以通过指定组件名称,来指定不销毁的组件:

<!-- 缓存一个组件 -->
<RouterView v-slot="{ Component }">
    <KeepAlive include="MessagePage">
        <component :is="Component" />
    </KeepAlive>
</RouterView>

<!-- 缓存多个组件,使用:include -->
<RouterView v-slot="{ Component }">
    <KeepAlive :include="['MessagePage', 'SettingPage']">
        <component :is="Component" />
    </KeepAlive>
</RouterView>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2 新的生命周期函数

在上面已经可以将组件缓存了,但是又可能引出一个新的问题。

例如,我们在组件的 onMounted 声明周期函数中启动了一个定时器,在 onBeforeMount 生命周期中销毁这个定时器,但是因为使用了 <KeepAlive> ,导致不会销毁组件,那么也不会调用 onBeforeMount函数,所以看不到组件的时候,定时器也是在不停的执行。

针对这个问题,引入了两个新的生命周期函数:

<template>
    <div>
        <div>消息</div>
        <input type="text" />
    </div>
</template>

<!-- setup -->
<script lang="ts" setup>

import { onActivated, onDeactivated } from 'vue';

onActivated(() => {
    console.log('组件处于活动状态');
});

onDeactivated(() => {
    console.log('组件处于非活动状态');
});

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

onActivatedonDeactivated 是 Vue Router 特有的导航守卫(navigation guards),它们专门用于 <KeepAlive> 包裹的组件中。这些守卫不是 Vue 核心功能的一部分,而是 Vue Router 提供的,用于处理组件的激活和停用状态,所以上面提到的问题,可以使用这两个生命周期回调来处理。

# 18.8 路由守卫

路由守卫(也称为导航守卫)是在路由跳转过程中触发的钩子函数,它们允许开发者在路由跳转前后执行特定的逻辑,如权限验证、页面跳转拦截、组件数据预加载等。

例如用户点击了某个链接,这个链接的功能需要登录,那么就给用户跳转到登录页面。

Vue 中的路由守卫主要分为三大类:全局守卫、路由独享守卫和组件内守卫。下面分别介绍这三类守卫。

# 1 全局守卫

全局守卫作用于整个应用,当路由发生变化时,这些守卫都会被触发。全局守卫主要包括三个回调钩子:

  • beforeEach
  • beforeResolve
  • afterEach

我们可以在项目的路由文件 src/router/index.ts 中进行配置。

# 全局前置守卫beforeEach

这个钩子会在每次路由导航之前触发。可以用于进行用户认证、权限检查等操作。这个是最常用的

/* eslint-disable */
import { createRouter, createWebHistory } from "vue-router";
import HomePage from "@/pages/HomePage.vue";

// 配置路由
const routes = [
  {
    path: "/",
    redirect: "/home", // 重定向到首页
  },
  {
    path: "/home",
    name: "home",
    component: HomePage,
    meta: { requiresAuth: true }, // 配置 meta,其中的属性可以自定义
  },
  {
    path: "/login",
    name: "login",
    component: () => import("@/pages/LoginPage.vue"),
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

/**
 * 前置路由守卫
 */
router.beforeEach((to, from, next) => {
  // 进行一些操作,比如检查用户是否已登录
  if (to.meta.requiresAuth && !isUserLoggedIn()) {
    console.log('跳转到登录');
    next({ path: "/login" }); // 重定向到登录页
  } else {
    // 调用 next() 方法来放行,否则路由不会跳转
    next();
  }
});

function isUserLoggedIn() {
  // 根据实际情况判断是否登录,可以从浏览器的 localStorage 或其他地方获取登录信息
  return false;
}

export default router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

上面先读取了 to.meta.requiresAuth 判断这个功能是否需要登录,如果这个路由是需要登录的,就使用 isUserLoggedIn 判断是否登录了,这个都是自定义的。

我们可以在路由中配置一下 meta.requiresAuthmeta 是路由对象的属性,我们可以在其中存储数据,然后在路由守卫中获取 meta 中的数据,然后进行判断。

这样在跳转到 /home ,在 router.beforeEach 中验证 to.meta.requiresAuth 就是需要登录了。

# 全局解析守卫beforeResolve

这个钩子在 beforeEach 和组件内守卫被解析之后,且在导航被确认之前执行。它可以用于异步数据获取等操作。

这个回调不常用,用不到就不用配置。

router.beforeResolve((to, from, next) => {
  // 在导航被确认之前执行一些操作
  next()
})
1
2
3
4

# 全局后置守卫afterEach

这个钩子在路由跳转完成后调用。可以用于进行一些收尾工作,比如停止加载动画等。该守卫没有 next 方法,因为路由跳转已经完成。

这个回调也用的不多,但是如果想要跳转完成,根据页面修改浏览器标签的 title,那么可以使用这种方式。

router.afterEach((to, from) => {
  // 在导航完成后执行一些操作
  document.title = to.meta.title || '逗比笔记'
})
1
2
3
4

同样,也需要在路由中的 meta 中配置 title

{
    path: "/home",
    name: "home",
    component: HomePage,
    meta: {requiresAuth: true, title: '首页'},
},
1
2
3
4
5
6

# 2 路由独享守卫

路由独享守卫只作用于特定的路由实例,不像全局守卫那样作用于所有路由。

所以需要配置在指定的路由中,使用 beforeEnter, 如下:

/* eslint-disable */
import { createRouter, createWebHistory } from "vue-router";
import HomePage from "@/pages/HomePage.vue";

// 配置路由
const routes = [
  {
    path: "/",
    redirect: "/home", // 重定向到首页
  },
  {
    path: "/home",
    name: "home",
    component: HomePage,
    meta: { requiresAuth: true, title: '首页' }, // 配置 meta,其中的属性可以自定义
    beforeEnter: (to: any, from: any, next: any) => {  // 路由独享守卫
      console.log('路由独享守卫');
      // 逻辑处理...  
      next();
  }
  },
  {
    path: "/login",
    name: "login",
    component: () => import("@/pages/LoginPage.vue"),
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

路由独享守卫只有前置没有后置守卫。

# 3 组件内守卫

组件内守卫是定义在组件内部的守卫,它们只在当前组件的路由发生变化时触发。

需要注意,只有通过路由规则进入组件,回调才会被调用,通过标签引入该组件,不会触发调用。

组合式 API 主要包含了两个函数回调:

  • onBeforeRouteUpdate:在当前路由改变时被调用。当使用同一个组件实例渲染不同的路由时,该钩子会被调用。
  • onBeforeRouteLeave:在导航离开组件的路由时调用。可以用来阻止导航(直接修改浏览器地址不行)。

举个栗子:

<template>
    <div class="home">
        <div>首页</div>
    </div>
</template>

<script setup lang="ts">
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router';

onBeforeRouteUpdate((to, from) => {
    // 路由参数变化时执行一些操作
    console.log('路由更新了', to, from);
});

onBeforeRouteLeave((to, from) => {
    console.log('要离开路由');
    const answer = window.confirm('确定要离开吗?');
    if (!answer) {
        return false;  // 返回 false 阻止导航
    }
});

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 18.9 路由模式

Vue 中的路由模式主要分为两种:hash 模式history 模式。这两种模式在单页面应用(SPA)中用于管理页面的路由和组件的展示。

# 1 Hash模式

特点

  • URL 中带有 # 符号,例如 http://localhost:8080/#/home
  • # 及其后面的字符称为 hashhash 的变化不会导致浏览器向服务器发送请求,因此页面不会重新加载。
  • 路由的切换实际上是通过监听 URL 的 hash 值变化来实现的,当 hash 值变化时,前端会根据这个变化来渲染对应的组件。

优点

  • 兼容性好,所有浏览器都支持 hash 路由。
  • 无需服务器端配置。

缺点

  • URL 看起来不够美观,带有 # 符号。

# 2 History模式

特点

  • URL 中不包含 # 符号,例如 http://localhost:8080/home
  • 路由的切换是通过 HTML5 History API 实现的,这种方式可以实现 URL 的无刷新跳转。
  • 浏览器地址栏中的 URL 会随着路由的切换而变化,但页面不会重新加载。

优点

  • URL 看起来更加美观,和传统的网页地址一样。
  • 用户体验更好,因为用户不会看到 URL 中的 # 符号。

缺点

  • 兼容性相对较差,一些旧的浏览器可能不支持 HTML5 History API。
  • 应用打包部署到服务后,需要服务器端的支持,否则刷新页面会出现 404 错误。可以在 Nginx 中进行配置:
location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files  $uri $uri/ /index.html;  # 需要添加如下配置
}
1
2
3
4
5

# 3 配置方式

在 Vue 中,使用 vue-router 时可以通过在创建 VueRouter 实例时设置路由模式。例如:

设置history模式

const router = createRouter({
  // 配置路由模式为history
  history: createWebHistory(),
  routes: [
    // 配置路由
  ],
});
1
2
3
4
5
6
7

设置hash模式

const router = createRouter({
  // 配置路由模式为hash
  history: createWebHashHistory(),
  routes: [
    // 配置路由
  ],
});
1
2
3
4
5
6
7

在使用 Vue Router 时,createWebHashHistorycreateWebHistory 可以接收一个可选的基路径参数。如果你将应用部署在一个子路径上(例如 http://www.doubibiji.com/my-app/),而不是根路径(例如 https://www.doubibiji.com/),那么你需要设置 BASE_URL/my-app/。这样路由系统将以这个路径作为所有路由的基础路径。

可以这样设置:

history: createWebHistory(import.meta.env.BASE_URL),
history: createWebHashHistory(import.meta.env.BASE_URL),
1
2

设置 BASE_URL 可以通过修改 Vite 的配置文件 vite.config.js 中的 base 选项来实现:

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  base: '/my-app/', // 设置应用的基路径
  // ... 其他配置
});

1
2
3
4
5
6
7
8
9
10

如果针对生产、测试、开发环境使用不同的配置,后面在多环境配置中再讲。