在 Vue3 中使用路由

路由是 Vue3 开发中的重要部分,它允许页面的部分改变,本文从简单静态路由、路由工作模式、动态路由、多级路由、编程式路由等全面介绍了 Vue3 的路由方式

常见的需求是,一个网页由 导航栏 + 内容页 组成:

image-20240217165249531

对于这种结构的网站来说,切换导航栏意味着内容页的变化,而不改变其他部分,在 Vue3 中,通过路由实现上面的需求:

image-20240217153357169

简单静态路由

第一步,创建组件

路由控制的展示页面,其实质是一个个组件,与一般的组件并无任何不同。

Home.vue

<script setup lang="ts">

</script>

<template>
  <div class="home">
    <h2>这是首页</h2>
  </div>
</template>

<style scoped>
.home {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}
</style>

News.vue

<script setup lang="ts">

</script>

<template>
  <div class="news">
    <ul>
      <li><a href="#">新闻</a></li>
      <li><a href="#">新闻</a></li>
      <li><a href="#">新闻</a></li>
      <li><a href="#">新闻</a></li>
      <li><a href="#">新闻</a></li>
    </ul>
  </div>
</template>

<style scoped>
.news {
  padding: 0 20px;
  display: flex;
  justify-content: space-between;
  height: 100%;
}
</style>

About.vue

<script setup lang="ts">

</script>

<template>
  <div class="about">
    <h2>这是关于页面</h2>
  </div>
</template>

<style scoped>
.about {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  color: gray;
  font-size: 18px;
}
</style>

注意:

  • 一般组件:通过标签 <Demo /> 引入组件
  • 路由组件:靠路由规则渲染出来,不显式存在组件标签 <Demo />,可能存在 to="/demo"
  • 路由组件虽然与普通组件本质上没什么不同,但工程上通常把路由组件放在 pages 或者 views 文件夹以区分一般组件的 components

第二步,配置路由的规则并使用

Vue3 允许我们使用 createRouter 创建路由器,并配置路由规则工作模式等路由器的行为模式。在配置路由后,别忘了在使用路由的应用中配置使用路由器。

/* 创建一个路由器,并暴露出去 */
import {createRouter, createWebHistory} from 'vue-router';
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import About from "@/pages/About.vue";

// 创建路由器
const router = createRouter({
    history: createWebHistory(), // 路由器的工作模式,见后续章节
    routes: [ // 路由规则
        {
            path: '/home',
            component: Home
        },
        {
            path: '/news',
            component: News
        },
        {
            path: '/about',
            component: About
        }
    ]
});

// 暴露路由
export default router;

main.ts 中,通过下面的代码引入前面设置的路由到 app 上

import {createApp} from "vue";
import App from "@/App.vue";
import router from '@/router';

// 创建一个 Vue 应用实例
const app = createApp(App);

// 使用路由
app.use(router);

// 挂载到根元素上
app.mount("#app")

第三步,配置路由的页面位置和跳转按钮

通过 RouterView 可以配置路由组件需要展示的位置,通过 RouterLink 可以配置跳转按钮,并通过 to 配置目标路由,active-class 配置生效时的样式。

<script setup lang="ts">
</script>

<template>
  <div class="app">
    <!--  网站标题  -->
    <div class="title">Vue3 路由</div>
    <!--  导航区  -->
    <div class="navi">
      <RouterLink to="/home" active-class="active">首页</RouterLink>
      <RouterLink to="/news" active-class="active">新闻</RouterLink>
        
      <RouterLink :to="{path: '/about'}" active-class="active">关于我们</RouterLink>
    </div>

    <!--  展示区  -->
    <div class="main-content">
      <!-- 对于路由组件,使用RouterView组件来展示  -->
      <RouterView/>
    </div>
  </div>
</template>

<style scoped>
.app {
  width: 100%;
  height: 100%;
  background-color: white;
}

.title {
  text-align: center;
  word-spacing: 5px;
  margin: 30px auto 0;
  height: 70px;
  line-height: 70px;
  background-image: linear-gradient(45deg, gray, white);
  border-radius: 10px;
  box-shadow: 0 0 2px;
  font-size: 30px;
  width: 90%;
}

.navi {
  display: flex;
  justify-content: space-around;
  margin: 30px auto 0;
  width: 90%;
}

.navi a {
  display: block;
  text-align: center;
  width: 100%;
  height: 40px;
  line-height: 40px;
  border: white solid;
  background-color: gray;
  text-decoration: none;
  color: white;
  font-size: 18px;
  letter-spacing: 5px;
}

.navi a.active {
  background-color: #64967e;
  color: #ffc268;
  font-weight: 900;
  text-shadow: 0 0 1px black;
}

.main-content {
  margin: 30px auto 0;
  border-radius: 10px;
  width: 90%;
  height: 200px;
  border: 1px solid;
}
</style>

注意:通过点击导航,视觉上「消失」了的路由组件,默认是被销毁掉的,需要的时候会重新挂载

路由器的工作模式

路由器的工作模式一共有两种,分别是:

history 模式

更常用,to C

const router = createRouter({
    history: createWebHistory(), // history模式
    ...

路径:http://localhost:5173/about

  • 优点:URL 更美观,不带 #,更接近传统网站 URL。
  • 缺点:后期项目上线,需要服务端配合处理路径问题(配置 nginx 的 try_files),否则刷新会出现 404 报错。

hash 模式

后台管理常用,不需要美观,无需 SEO 优化

const router = createRouter({
    history: createWebHashHistory(), // hash模式
    ...

路径:http://localhost:5173/#/about

  • 优点:兼容性更好,不需要服务端处理路径。
  • 缺点:URL 带有 # 不太美观,在 SEO 方面优化相对较差

路由的历史记录模式

路由的历史记录模式配置的是进行路由的切换后,在浏览器中以何种形式展现这个记录。简单来说,就是控制用户能否通过浏览器的回退键回到上一页的行为

push 模式

默认的模式,无需任何配置。路由的切换会被 push 到历史记录栈中去,通过回退键能够返回到上一页。

replace 模式

通过在 RouterLink 中配置 replace 属性实现,路由的切换会直接覆盖历史记录栈的顶部,也就是说,通过回退键不能返回上一页。

<RouterLink replace :to="{name:'home'}" active-class="active">首页</RouterLink>

更复杂地路由

:to 动态路由

前面介绍了通过设定 to 的地址确定目标,这是一种静态的路由方式,实际上,Vue3 允许我们向 to 传递一个对象,例如:

<RouterLink :to="{path: '/about'}" active-class="active">关于我们</RouterLink>

通过这种方式,可以更加灵活地实现路由的跳转。

name 命名路由

在配置路由时,除了设定路由的地址 path,其实,还可以设置路由的名称,例如:

const router = createRouter({
    history: createWebHistory(),
    routes: [
        {
            name: 'anyName', // 配置路由的名称
            path: '/home',
            component: Home
        },
        ...

配置了名称的路由,可以通过传递带 name 的对象给 to 实现根据名称跳转——当然本质上还是通过 path 跳转:

<RouterLink :to="{name:'anyName'}" active-class="active">首页</RouterLink>

命名路由的意义在于,当 path 很长时,提供了一种更加简洁的编写方式,换言之,命名路由实质上是一种语法糖。

redirect 路由重定向

可以将一个路由地址重定向到另一个路由地址,通过配置 redirect 实现。

// 创建路由器
const router = createRouter({
    history: createWebHistory(), // 路由器的工作模式
    routes: [ // 路由规则
        {
            name: 'home',
            path: '/home',
            component: Home
        },
        {
            path: '/',
            redirect: '/home' // 重定向到首页
        },
        ...
    ]
});

多级路由

有时,我们在某一个路由组件中,需要再做路由,例如:

image-20240217182327682

在 Vue3 中, 可以通过 children 定义路由的子路由,从而实现多级路由。

多级路由示例

例如我们可以修改 简单静态路由 一节的代码,实现在新闻组件下继续点击每一条具体新闻时,在右侧显示新闻的具体内容:

image-20240217183817626

此时,我们可以修改路由配置的代码,新增一个子路由:

// 创建路由器
const router = createRouter({
    history: createWebHistory(), // 路由器的工作模式
    routes: [ // 路由规则
        ...
        {
            name: 'news',
            path: '/news',
            component: News,
            children: [ // 子路由
                {
                    name: "detail",
                    path: 'detail', // 此处不要加'/',才能通过 '/news/detail' 访问此组件
                    component: NewsDetail
                }
            ]
        },
        ...

对于多级路由的访问,与静态是完全一致的,例如在 News.vue 中:

<template>
  <div class="news">
    <!--  导航区  -->
    <ul>
      <li v-for="news in newsList" :key="news.id">
        <RouterLink to="/news/detail">{{ news.title }}</RouterLink>
      </li>
    </ul>

    <!--  展示区  -->
    <div class="news-content">
      <RouterView/>
    </div>
  </div>
</template>

query 传参

query 是通过 HTTP 请求的 query 参数传递给路由组件,在路由组件中可以使用 useRoute.query 获取对应的参数。通过这种方式传参,会在请求的参数列表中,带上查询的参数,例如:

http://localhost:5173/news/detail?id=adiojo02&title=好消息!&content=快过年了

发送方式

第一种发送方式,通过 js 的字符串模板拼接:

<!--  导航区  -->
<ul>
  <li v-for="news in newsList" :key="news.id">
    <RouterLink :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">
      {{ news.title }}
    </RouterLink>
  </li>
</ul>

第二种发送方式,通过传递对象:

    <!--  导航区  -->
    <ul>
      <li v-for="news in newsList" :key="news.id">
        <RouterLink :to="{
          name: 'detail',
          // 或者基于 path
          // path: '/news/detail',
          query: {
            id: news.id,
            title: news.title,
            content: news.content
          }
        }">
          {{ news.title }}
        </RouterLink>
      </li>
    </ul>

接收方式

通过 useRoute().query 获取。

NewsDetail.vue:

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

const route = useRoute();

</script>

<template>
  <ul class="news-list">
    <li>编号:{{ route.query.id }}</li>
    <li>标题:{{ route.query.title }}</li>
    <li>内容:{{ route.query.content }}</li>
  </ul>
</template>

<style scoped>
.news-list {
  list-style: none;
  padding-left: 20px;
}

.news-list > li {
  line-height: 30px;
}
</style>

params 传参

params 传参,相较于 query 传参更加简洁。它是 RestFul 风格的传参方式,通过这种方式传参,形如:

http://localhost:5173/news/detail/adiojo02/好消息!/快过年了

配置路由

params 传参不是 query 那样的 key-value 式传参,因此需要事先规定好每个位置的含义,配置好 params 传参的接收方式,并与路由做区分。

支持通过在参数末尾加 ? 来控制参数的必要性,默认所有参数都必需

// 创建路由器
const router = createRouter({
    history: createWebHistory(), // 路由器的工作模式
    routes: [ // 路由规则
        ...
        {
            name: 'news',
            path: '/news',
            component: News,
            children: [
                {
                    name: "detail",
                    path: 'detail/:id/:title/:content?', // 占位,配置 params 传参的接收方式
                    component: NewsDetail
                }
            ]
        },
        ...

发送方式

第一种发送方式,通过 js 字符串拼接:

<ul>
  <li v-for="news in newsList" :key="news.id">
    <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">
      {{ news.title }}
    </RouterLink>
  </li>
</ul>

第二种发送方式,通过传递对象:

<!--  导航区  -->
<ul>
  <li v-for="news in newsList" :key="news.id">
    <RouterLink :to="{
              // 不支持基于 path
              name: 'detail',
              params: {
                id: news.id,
                title: news.title,
                content: news.content
              }
            }">
      {{ news.title }}
    </RouterLink>
  </li>
</ul>

注意:

  1. 对于 params 传参不支持 path 路由。
  2. 参数不支持传送数组、对象类型。

接收方式

完全类似 query 传参,通过 useRoute().param 获取 params 参数。

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

const route = useRoute();

</script>

<template>
  <ul class="news-list">
    <li>编号:{{ route.params.id }}</li>
    <li>标题:{{ route.params.title }}</li>
    <li>内容:{{ route.params.content }}</li>
  </ul>
</template>

<style scoped>
.news-list {
  list-style: none;
  padding-left: 20px;
}

.news-list > li {
  line-height: 30px;
}
</style>

用路由规则的 props 简化传参

前面我们介绍了使用 query 和使用 params 传参的两种方式,但不管是哪种参数,使用的时候都会出现形如 route.params.id 形式,这不够简洁,有没有方式能简化参数的使用呢?

用 js 的解构(不推荐)

当然,我们可以使用 js 的解构简化参数的使用:

let {params} = toRefs(route); // 这里的 toRefs 是必须的,作用是保留 params 的相应式

但是,使用时,还是需要:

  <ul class="news-list">
    <li>编号:{{ params.id }}</li>
    <li>标题:{{ params.title }}</li>
    <li>内容:{{ params.content }}</li>
  </ul>

诚然,我们可以再解构一遍,但这太过麻烦。

通过路由规则的 props 简化传参

Vue3 提供了更加简易的传参方式,这是通过路由规则配置实现的,其支持三种写法:

  1. 简单写法

    // 创建路由器
    const router = createRouter({
        history: createWebHistory(), // 路由器的工作模式
        routes: [ // 路由规则
            ...
            {
                name: 'news',
                path: '/news',
                component: News,
                children: [
                    {
                        name: "detail",
                        path: 'detail/:id/:title/:content',
                        component: NewsDetail,
                        // 第一种写法,将路由收到的所有 params 参数都传递给 NewsDetail 组件
                        // 不支持 query 参数
                        props: true
                    }
                ]
            },
            ...
    
  2. 函数式写法(推荐)

    // 创建路由器
    const router = createRouter({
        history: createWebHistory(), // 路由器的工作模式
        routes: [ // 路由规则
            ...
            {
                name: 'news',
                path: '/news',
                component: News,
                children: [
                    {
                        name: "detail",
                        path: 'detail/:id/:title/:content',
                        component: NewsDetail,
    
                        // 第二种写法,可以自己决定将哪些参数传递给 NewsDetail 组件
                        // 支持 query 参数
                        props: (route) => {
                            return route.params; // 或者 route.query 都可以,这里的 route 就是前面的 useRoute()
                        }
                    }
                ]
            },
            ...
    
  3. 对象写法(不常用)

    // 创建路由器
    const router = createRouter({
        history: createWebHistory(), // 路由器的工作模式
        routes: [ // 路由规则
            ...
            {
                name: 'news',
                path: '/news',
                component: News,
                children: [
                    {
                        name: "detail",
                        path: 'detail/:id/:title/:content',
                        component: NewsDetail,
    
                        // 第三种写法,对象形式(用得少)
                        props: {
                            id: '123',
                            title: 'title',
                            content: 'content'
                        }
                    }
                ]
            },
            ...
    

配置完 props: true 后,Vue3 会自动将 path 配置的参数以 props 的形式传递给组件,此时,在组件中只需通过 defineProps 接收父组件传递的参数即可:

<script setup lang="ts">
defineProps(
    ['id', 'title', 'content']
);
</script>

<template>
  <ul class="news-list">
    <li>编号:{{ id }}</li>
    <li>标题:{{ title }}</li>
    <li>内容:{{ content }}</li>
  </ul>
</template>

由于传参和 props 是等价的,限制接收类型和默认值(见前面讲 props 的章节)等方式都是有效的。

编程式导航(重要)

细心的你可能发现,RouterLink 最终会被翻译成 a 标签,所以前面针对 a 标签的样式会对 RouterLink 生效。可是,有时我们希望通过其他组件、按钮、事件触发路由组件的变化,这时我们就需要用到编程式导航

实际上,我们可以通过 useRouter 获取路由本身,使用其中的 replace(to)push(to) 等函数操作路由跳转。其中 to 的内容和 RouterLink 的类似,可以用字符串,可以用对象,可以传参

注意区分 useRouteruseRoute

例如:进入首页 3s 后,跳转到关于页面。

<script setup lang="ts">
import {onMounted} from "vue";
import {useRouter} from "vue-router";

// 获取路由本身
const router = useRouter();

onMounted(
    () => {
      setTimeout(() => {
        console.log('3s 到了')

        // 让路由实现跳转
        router.push({name: 'about'}) // push 模式跳转
        // router.replace('/about') // replace 模式跳转
      }, 3000);
    }
);
</script>

<template>
  <div class="home">
    <h2>这是首页</h2>
  </div>
</template>

<style scoped>
.home {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}
</style>