4. Vue Router 路由管理:让页面跳转变得优雅

单页应用(SPA)的核心,说白了就是「页面不刷新,内容随便换」。Vue Router 就是帮我们实现这件事的官方工具。我刚开始用 Vue 做项目时,总觉得路由就是个跳转链接,后来踩了几个坑才明白——路由设计的好坏,直接决定了项目的可维护性。

这一章,我们一步步把 Vue Router 装进语音识别前端系统里。从安装配置,到嵌套路由,再到导航守卫,我会把项目中实际遇到的问题都告诉你。

4.1 安装与基础配置

先装包。用 npm 或 yarn 都行,我个人习惯用 npm:

npm install vue-router@4

装完之后,在 src 目录下新建一个 router 文件夹,里面放 index.js。这是路由的配置文件,所有页面路径都在这定义。

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
]

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

export default router

然后在 main.js 里注册:

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

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

嗯,这里要注意:createWebHistory 用的是 HTML5 的 History 模式,URL 里没有 #,看着清爽。但如果你部署到生产环境,需要后端配合做重定向,否则刷新页面会 404。我之前有个项目上线忘了配,结果用户一刷新就白屏……那场面,别提多尴尬了。

小技巧:开发环境用 createWebHistory 没问题。如果后端不好配,可以改用 createWebHashHistory,URL 里会带 #,但永远不会 404。

4.2 页面跳转:声明式 vs 编程式

Vue Router 提供了两种跳转方式。先看声明式,就是在模板里用 <router-link>:

<!-- 在 App.vue 或任何组件里 -->
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>

<!-- 还可以传参数 -->
<router-link :to="{ name: 'About' }">关于(命名路由)</router-link>

另一种是编程式导航,在 JavaScript 里用 router.push:

// 在某个方法里
function goToAbout() {
  router.push('/about')
}

// 或者带参数
router.push({ name: 'About', query: { source: 'home' } })

我个人更推荐用命名路由(name)而不是写死路径。为什么?因为路径可能会改,但名字一般不变。你想想看,如果项目做到一半,产品经理说「首页路径改成 /home 吧」,你只需要改一处配置,所有跳转都不用动。

核心原则:能用 name 就别用 path。这是我在多个项目中总结出的血泪教训。

4.3 嵌套路由:页面里的子页面

语音识别系统里,肯定有「用户中心」这种模块。用户中心下面可能有「个人资料」「我的录音」「设置」等子页面。这就是嵌套路由的典型场景。

先定义父路由,里面用 children 配置子路由:

const routes = [
  {
    path: '/user',
    name: 'User',
    component: () => import('../views/User.vue'),
    children: [
      {
        path: 'profile',
        name: 'UserProfile',
        component: () => import('../views/UserProfile.vue')
      },
      {
        path: 'recordings',
        name: 'UserRecordings',
        component: () => import('../views/UserRecordings.vue')
      },
      {
        path: 'settings',
        name: 'UserSettings',
        component: () => import('../views/UserSettings.vue')
      }
    ]
  }
]

然后在 User.vue 里放一个 <router-view>,子页面就会渲染到这里:

<!-- User.vue -->
<template>
  <div class="user-container">
    <nav>
      <router-link to="/user/profile">个人资料</router-link>
      <router-link to="/user/recordings">我的录音</router-link>
      <router-link to="/user/settings">设置</router-link>
    </nav>
    <router-view />
  </div>
</template>

注意子路由的 path 前面不要加 /,否则会变成绝对路径。我曾经犯过这个错,子路由怎么跳都跳到根路径去,查了半天才发现是多了一个斜杠。

避坑指南:子路由的 path 不要以 / 开头。加了 / 就是绝对路径,会脱离父路由的上下文。

4.4 导航守卫:控制谁能进哪个页面

语音识别系统里,有些页面需要登录才能访问。比如「我的录音」页面,没登录的用户应该被重定向到登录页。导航守卫就是干这个的。

Vue Router 提供了三种守卫:全局守卫、路由独享守卫、组件内守卫。最常用的是全局前置守卫 beforeEach:

// src/router/index.js
router.beforeEach((to, from, next) => {
  // 检查目标路由是否需要登录
  if (to.meta.requiresAuth) {
    // 假设我们有个 auth 模块来判断登录状态
    const isLoggedIn = localStorage.getItem('token')
    if (isLoggedIn) {
      next()
    } else {
      next({ name: 'Login' })
    }
  } else {
    next()
  }
})

然后在路由配置里加 meta 字段:

{
  path: '/user/recordings',
  name: 'UserRecordings',
  component: () => import('../views/UserRecordings.vue'),
  meta: { requiresAuth: true }
}

这样,没登录的用户访问 /user/recordings 时,会自动跳到登录页。登录成功后,还可以用 router.replace 回到之前想去的页面。

除了全局守卫,还有路由独享守卫 beforeEnter:

{
  path: '/admin',
  component: Admin,
  beforeEnter: (to, from, next) => {
    // 只有管理员能进
    const role = localStorage.getItem('role')
    if (role === 'admin') {
      next()
    } else {
      next({ name: 'Home' })
    }
  }
}

组件内守卫用得少一些,但有个场景很实用:用户编辑表单时,不小心点了返回,你想弹个确认框。用 beforeRouteLeave 就能拦截:

// 在组件内部
beforeRouteLeave(to, from, next) {
  if (this.hasUnsavedChanges) {
    const answer = window.confirm('你有未保存的修改,确定要离开吗?')
    if (answer) {
      next()
    } else {
      next(false)
    }
  } else {
    next()
  }
}
记住:守卫里的 next() 一定要调用,否则页面会卡住不动。我第一次写守卫时忘了调 next(),结果页面白屏,控制台也没报错,排查了半天……

4.5 路由设计:语音识别系统的实际案例

好了,理论说完了。我们来看看语音识别前端系统的路由该怎么设计。下面这张图是我画的路由结构:

语音识别前端系统路由结构图 语音识别前端系统 - 路由结构 / (根路径) Home (首页) Login (登录) User (用户中心) Profile (个人资料) Recordings (录音列表) Settings (设置) RecordingDetail 路由设计要点: 1. 首页 (/) 和登录页 (/login) 是独立路由,不需要登录 2. 用户中心 (/user) 及其子路由需要登录,用导航守卫控制 3. 录音详情 (/recording/:id) 是动态路由,从首页或录音列表跳转过来 4. 所有未匹配的路由统一重定向到 404 页面

对应的路由配置大概是这样的:

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/Login.vue')
  },
  {
    path: '/recording/:id',
    name: 'RecordingDetail',
    component: () => import('../views/RecordingDetail.vue'),
    props: true  // 把 params 作为 props 传给组件
  },
  {
    path: '/user',
    name: 'User',
    component: () => import('../views/User.vue'),
    meta: { requiresAuth: true },
    children: [
      {
        path: 'profile',
        name: 'UserProfile',
        component: () => import('../views/UserProfile.vue')
      },
      {
        path: 'recordings',
        name: 'UserRecordings',
        component: () => import('../views/UserRecordings.vue')
      },
      {
        path: 'settings',
        name: 'UserSettings',
        component: () => import('../views/UserSettings.vue')
      }
    ]
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('../views/NotFound.vue')
  }
]

注意最后那个 /:pathMatch(.*)*,这是 Vue Router 4 的 404 通配写法。Vue Router 3 用的是 *,升级到 4 后改成了正则匹配。我第一次升级项目时没注意这个,结果所有未匹配路径都白屏了。

关于动态路由:/recording/:id 里的 :id 是动态参数。在组件里用 route.params.id 获取。加上 props: true 后,可以直接用 props 接收,更符合 Vue 的组件设计理念。

4.6 路由懒加载:让首屏更快

语音识别系统里,有些页面(比如录音详情)可能包含很大的组件。如果所有页面都打包到一个文件里,首屏加载会变慢。用路由懒加载可以按需加载,只有访问到某个路由时才加载对应的组件。

实现方式很简单,把 component 改成箭头函数 + import():

{
  path: '/recording/:id',
  name: 'RecordingDetail',
  component: () => import('../views/RecordingDetail.vue')
}

Webpack 或 Vite 会自动把每个懒加载的路由打包成独立的 chunk。你可以在浏览器开发者工具的 Network 面板里看到,访问新路由时会多一个请求。

我个人习惯把所有路由都写成懒加载,除了首页。首页是用户第一个看到的页面,预加载能减少白屏时间。其他页面用户不一定访问,按需加载就好。

性能优化:懒加载 + 命名 chunk,可以让每个页面的代码独立打包。配合 <link rel="prefetch">,还能在空闲时预加载用户可能访问的页面。

4.7 总结一下

Vue Router 的核心就三件事:配置路由、实现跳转、控制访问。配置路由时注意嵌套和动态参数;跳转时优先用命名路由;控制访问用导航守卫,别忘了调 next()。

语音识别系统的路由设计,我建议你从首页和登录页开始,逐步添加用户中心和录音详情。每加一个路由,都想想它需不需要登录、有没有子页面、要不要懒加载。这样一步步来,不会乱。

嗯,这一章的内容就到这。路由配好了,下一步就是页面之间的数据传递和状态管理了。