Skip to content

基础路由 vue-router@4.2.5

Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。功能包括:

  • 嵌套路由映射
  • 动态路由选择
  • 模块化、基于组件的路由配置
  • 路由参数、查询、通配符
  • 展示由 Vue.js 的过渡系统提供的过渡效果
  • 细致的导航控制
  • 自动激活 CSS 类的链接
  • HTML5 history 模式或 hash 模式
  • 可定制的滚动行为
  • URL 的正确编码

两个注意点

  1. 路由组件通常存放在pages或者views文件夹,一般组件通常存放在components文件夹
  2. 通过点击导航, 视觉效果上"消失"了的路由组件,默认是被销毁掉的, 需要的时候再去挂载

1. 入门

如果项目中还没有安装vue-router 需要使用包管理器安装一下:

bash
pnpm add vue-router

TIP

VUE3 使用的路由版本是 vue-router@4, 安装时使用默认最新版即可

router-link: 使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。

router-view: 显示与 URL 对应的组件。你可以把它放在任何地方,以适应你的布局。

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

<template>
  <div class="app">
    <h2 class="title">Vue路由测试</h2>
    <!-- 导航区 -->
    <div class="navigate">

      <RouterLink to="/home" active-class="active">首页</RouterLink>
      <RouterLink to="/news" active-class="active">新闻</RouterLink>
      <RouterLink to="/about" active-class="active">关于</RouterLink>
    </div>
    <!-- 展示区 -->
    <div class="main-content">

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

TIP

别忘记在main.ts中,引入路由并使用

ts
import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

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

路由传参有 2 中方式,分别是queryparams,params 是路由的一部分,而 query 不会改变路由的路径,它通常用于查询字符串的参数传递。

1.1 命名路由

命名路由是 vue-router 中的一种特性,它允许你给路由规则指定一个名字,使得在应用程序中通过名称来标识和导航到特定的路由,而不是直接使用路径。

TIP

使用命名路由的好处是,当你需要改变路径时,你只需要改变路由规则中的路径,而不需要改动所有使用该路由的地方。这样可以提高代码的可维护性。

ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // ...其他路由规则
    {
      path: '/news',
      component: () => import('@/pages/News.vue'),
      children: [

        {
          name: 'xiangqing',
          path: 'detail/:id/:title/:content',
          component: () => import('@/pages/Detail.vue'),
        },
      ],
    },
  ],
})
html
<div class="news">
  <ul>
    <li v-for="news in newsList" :key="news.id">

      <RouterLink
        :to="{
            name: 'xiangqing',
            params: { id: news.id, title: news.title, content: news.content },
          }">
        {{ news.title }}
      </RouterLink>
    </li>
  </ul>
  <div class="news-content">
    <RouterView></RouterView>
  </div>
</div>

命名路由是 vue-router 中提高路由管理灵活性和可维护性的重要特性之一。

1.2 嵌套路由

嵌套路由是一种常见的特性,它允许你在一个路由组件内部定义更多的路由规则。这对于构建具有层级结构的用户界面非常有用,比如后台管理系统的左侧菜单和主内容区域,或者前端页面的页头、侧边栏和主内容区域等。

要使用嵌套路由,你需要在父路由组件中定义一个 <router-view>,这是子路由组件将被渲染的地方。然后,你可以在路由配置中为父路由定义一个 children 数组,数组中包含嵌套的路由规则。

下面是一个嵌套路由的基本示例:

ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // ...其他路由规则
    {
      path: '/news',
      component: () => import('@/pages/News.vue'),

      children: [
        {
          name: 'xiangqing', // 注意这里没有前斜杠,它是一个相对路径
          path: 'detail/:id/:title/:content',
          component: () => import('@/pages/Detail.vue'),
        },
      ],
    },
  ],
})
html
<div class="news">
  <ul>
    <li v-for="news in newsList" :key="news.id">

      <RouterLink
        :to="{
            name: 'xiangqing',
            params: { id: news.id, title: news.title, content: news.content },
          }">
        {{ news.title }}
      </RouterLink>
    </li>
  </ul>
  <div class="news-content">
    <RouterView></RouterView>
  </div>
</div>

嵌套路由的使用非常灵活,你可以在任何级别上嵌套路由,以构建复杂的页面结构。这在构建大型应用程序时非常有用,可以帮助你更好地组织代码和界面。

1.3 query 传参

query 传参通常用于搜索、排序等操作,这些参数不是路由的一部分,不会影响路由的匹配。它们以键值对的形式出现在 URL 的查询字符串中,例如 ?foo=bar&baz=qux

html
<template>
  <div class="news">
    <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> -->
        <!-- 第二种写法 对象写法-->
        <RouterLink
          :to="{
            path: '/news/detail',
            query: { id: news.id, title: news.title, content: news.content },
          }">
          {{ news.title }}
        </RouterLink>
      </li>
    </ul>
    <div class="news-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

<script setup lang="ts" name="News">
  import { reactive } from 'vue'

  const newsList = reactive([
    { id: 1, title: '新闻001', content: '新闻001的内容' },
    { id: 2, title: '新闻002', content: '新闻002的内容' },
    { id: 3, title: '新闻003', content: '新闻003的内容' },
    { id: 4, title: '新闻004', content: '新闻004的内容' },
    { id: 5, title: '新闻005', content: '新闻005的内容' },
    { id: 6, title: '新闻006', content: '新闻006的内容' },
  ])
</script>
html
<template>
  <ul class="news-list">

    <li>编号:{{ query.id }}</li>
    <li>标题:{{ query.title }}</li>
    <li>内容:{{ query.content }}</li>
  </ul>
</template>

<script setup lang="ts" name="Detail">

  import { useRoute } from 'vue-router'
  import { toRef } from 'vue'

  const route = useRoute()
  const query = toRef(route, 'query')
</script>
ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // ...省略无关路由
    {
      path: '/news',
      component: () => import('@/pages/News.vue'),
      children: [

        {
          name: 'xiangqing',
          path: 'detail',
          component: () => import('@/pages/Detail.vue'),
        },
      ],
    },
  ],
})
export default router

使用 query 传参的好处是它们不会改变路由的路径,因此不会触发路由的重新匹配。这对于不改变页面视图但需要改变数据的情况非常有用。例如,你可以使用 query 参数来保持用户在分页、筛选或排序操作中的位置。

1.4 params 传参

params 是路由的一部分,通常用于将必要的信息传递给路由组件,这些信息是动态的,并且会影响路由的匹配。params 会被包含在路由路径中,它们不会出现在 URL 的查询字符串中,而是直接嵌入到路由路径本身。

在定义路由时,你可以使用冒号 : 来标记路由路径中的动态部分,这些部分将成为 params:

html
<template>
  <div class="news">
    <ul>
      <li v-for="news in newsList" :key="news.id">
        <!-- 第一种写法 -->

        <!-- <RouterLink
          :to="`/news/detail/${news.id}/${news.title}/${news.content}`">
          {{ news.title }}
        </RouterLink> -->
        <!-- 第二种写法 -->
        <RouterLink
          :to="{
            name: 'xiangqing',
            params: { id: news.id, title: news.title, content: news.content },
          }">
          {{ news.title }}
        </RouterLink>
      </li>
    </ul>
    <div class="news-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

<script setup lang="ts" name="News">
  import { reactive } from 'vue'

  const newsList = reactive([
    { id: 1, title: '新闻001', content: '新闻001的内容' },
    { id: 2, title: '新闻002', content: '新闻002的内容' },
    { id: 3, title: '新闻003', content: '新闻003的内容' },
    { id: 4, title: '新闻004', content: '新闻004的内容' },
    { id: 5, title: '新闻005', content: '新闻005的内容' },
    { id: 6, title: '新闻006', content: '新闻006的内容' },
  ])
</script>
html
<template>
  <ul class="news-list">

    <li>编号:{{ params.id }}</li>
    <li>标题:{{ params.title }}</li>
    <li>内容:{{ params.content }}</li>
  </ul>
</template>

<script setup lang="ts" name="Detail">

  import { toRefs } from 'vue'
  import { useRoute } from 'vue-router'

  const route = useRoute()
  const { params } = toRefs(route)
</script>
ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // ...省略无关路由
    {
      path: '/news',
      component: () => import('@/pages/News.vue'),
      children: [

        {
          name: 'xiangqing',
          path: 'detail/:id/:title/:content',
          component: () => import('@/pages/Detail.vue'),
        },
      ],
    },
  ],
})
export default router

注意事项:

  • params 是必须的,如果你没有提供必要的 params,路由将无法匹配成功。
  • 由于 params 是路由的一部分,所以改变 params 会导致路由的跳转和组件的重新渲染。
  • params 不会出现在 URL 的查询字符串中,而是直接嵌入到路径中。

WARNING

传递params参数时, 若使用to的对象写法, 必须使用name配置项,不能使用path

传递params参数时,需要提前在规则中占位

1.5 路由的 props 参数

路由的 props 选项是一种将路由参数传递给组件的方式。这允许你在组件内部直接访问路由参数,而不必通过 $route 对象。这可以使得组件与路由解耦,使得组件更加可复用和易于测试。

有三种方式可以将路由 props 参数传递给组件:

  1. 布尔模式: 当 props 设置为 true 时,路由的params参数会被直接作为组件的 props 传递。该模式仅能传递 params 参数
html
<template>
  <div class="news">
    <ul>
      <li v-for="news in newsList" :key="news.id">

        <RouterLink
          :to="{
            name: 'xiangqing',
            params: { id: news.id, title: news.title, content: news.content },
          }">
          {{ news.title }}
        </RouterLink>
      </li>
    </ul>
    <div class="news-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>
ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // ...省略其他路由
    {
      path: '/news',
      component: () => import('@/pages/News.vue'),
      children: [
        {
          name: 'xiangqing',
          path: 'detail/:id/:title/:content',
          component: () => import('@/pages/Detail.vue'),
          // 第一种写法,将路由收到的所有params参数进行传递
          props: true,
        },
      ],
    },
  ],
})
export default router
html
<template>
  <ul class="news-list">
    <li>编号:{{ id }}</li>
    <li>标题:{{ title }}</li>
    <li>内容:{{ content }}</li>
  </ul>
</template>

<script setup lang="ts" name="Detail">
  import { toRef } from 'vue'
  import { useRoute } from 'vue-router'

  const route = useRoute()
  // 也可以用toRefs替代toRef, 注意写法的区别
  const params = toRef(route, 'params')

  defineProps(['id', 'title', 'content']) 
</script>
  1. 对象模式(几乎不使用): 当 props 是一个对象时,它将把这个对象的所有属性作为 prop 传递给组件。
ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // ...省略其他路由
    {
      path: '/news',
      component: () => import('@/pages/News.vue'),
      children: [
        {
          name: 'xiangqing',
          path: 'detail/:id/:title/:content',
          component: () => import('@/pages/Detail.vue'),
          // 第二种写法,直接传递静态数据
          props: { a: '100', b: '200', c: '300' },
        },
      ],
    },
  ],
})
export default router
html
<template>
  <ul class="news-list">

    <li>编号:{{ a }}</li>
    <li>标题:{{ b }}</li>
    <li>内容:{{ c }}</li>
  </ul>
</template>

<script setup lang="ts" name="Detail">
  defineProps(['a', 'b', 'c']) 
</script>

无论实际路由参数是什么,Detail 组件都将接收一个指定对象的 props。

  1. 函数模式: 当 props 是一个函数时,它允许你将路由参数转换为其他值,或者基于路由参数动态返回一个对象。
html
<template>
  <div class="news">
    <ul>
      <li v-for="news in newsList" :key="news.id">

        <RouterLink
          :to="{
            name: 'xiangqing',
            query: { id: news.id, title: news.title, content: news.content },
          }">
          {{ news.title }}
        </RouterLink>
      </li>
    </ul>
    <div class="news-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>
ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // ...省略其他路由
    {
      path: '/news',
      component: () => import('@/pages/News.vue'),
      children: [
        {
          name: 'xiangqing',
          path: 'detail',
          component: () => import('@/pages/Detail.vue'),

          // 第三种写法,可以自己决定传递什么参数
          props: route => route.query || {},
        },
      ],
    },
  ],
})
export default router

当你想用 props 传递 query 参数的时候 你可以返回route.query

使用 props 选项可以让组件更加独立于路由,这在创建可复用的组件时非常有用,因为你可以在不同的路由中使用相同的组件,而不需要担心组件如何获取其数据。

2. 路由的两种工作模式

如果你预计用户主要使用现代浏览器,推荐使用 history 模式,因为它提供了更美观和标准的 URL。譬如在线商城类项目

如果你的应用不需要 SEO,或者你的应用需要兼容旧版浏览器,或者你不想处理服务器端的配置,那么 hash 模式可能更适合。譬如后台管理类项目

2.1 Hash 模式

特点:

  • URL 中会包含 # 符号,例如 http://example.com/#/about。
  • 通过 hashchange 事件监听 URL 的变化。
  • 不需要服务器配置,因为 # 后的内容不会发送到服务器。
  • 对于老版本的浏览器有更好的兼容性。
ts
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
  history: createWebHashHistory(), // 使用 hash 模式
  routes: [
    // 路由配置...
  ],
})

2.2 History 模式

特点:

  • URL 不含 # 符号,看起来更像是常规的 URL,例如 http://example.com/about。
  • 通过 popstate 事件监听 URL 的变化。
  • 需要服务器进行额外的配置,因为直接访问某个路由时,服务器需要能够处理所有可能的 URL,如果没有对应的资- 源,服务器应该返回同一个页面,通常这个页面就是你的 index.html。
  • 对搜索引擎优化 (SEO) 更友好。
ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(), // 使用 history 模式
  routes: [
    // 路由配置...
  ],
})

附后端需要配置的操作:

nginx
location / {
  try_files $uri $uri/ /index.html;
}
apache
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>
js
const history = require('connect-history-api-fallback')
const express = require('express')
const app = express()

app.use(history())
app.use(express.static('public'))

app.listen(3000)
text
对于 GitHub Pages,你可以在仓库的根目录下添加一个名为 404.html 的文件,

其内容是你的入口页面 index.html 的副本。

这样,当 GitHub Pages 无法找到请求的页面时,它会返回 404.html,

从而允许 Vue Router 正常工作。

3. 路由的 replace 属性

在 Vue 应用中使用 replace 选项进行路由导航时,新的路由会替换掉当前的历史记录,而不是添加一个新的记录。

html
<router-link :to="{ name: 'home' }" replace>Home</router-link>

在这个例子中,点击这个链接将使用 replace 方法导航到 home 路由,而不会添加一个新的历史记录。

使用 replace 可以在某些情况下提供更好的用户体验,例如,当从登录页面重定向到主页时,你可能不希望用户能够通过后退按钮返回到登录页面。