Vue Router 4 路由进阶

Vue Router 4 路由进阶 -- pd的前端笔记

文章目录

一、为什么需要路由?

🎯 场景:一个多页面的后台管理系统

假设你的应用有:

  • 首页(/)
  • 用户管理(/users)
  • 订单管理(/orders)
  • 设置页面(/settings)

❌ 不用路由的写法(单组件切换)

html 复制代码
<template>
  <div>
    <button @click="currentPage = 'home'">首页</button>
    <button @click="currentPage = 'users'">用户</button>
    
    <Home v-if="currentPage === 'home'" />
    <Users v-if="currentPage === 'users'" />
  </div>
</template>

🔴 问题:

  • URL 不变,无法分享/收藏具体页面
  • 浏览器前进/后退按钮失效
  • 刷新后回到首页,丢失当前状态
  • SEO 不友好(搜索引擎无法索引)

✅ 用路由的写法

text 复制代码
访问 /users → 自动加载 Users 组件
访问 /orders → 自动加载 Orders 组件

🧠 类比:

手动切换组件像"翻书"------页码不变;

路由像"图书馆索书号"------每个页面有独立地址,可分享、可收藏、可回溯。

二、Vue Router 是什么?

🔎 先认识 Vue Router(Vue 官方路由库)

Vue Router 是什么?

它是 Vue.js 官方的前端路由管理器,与 Vue 核心深度集成,让构建单页面应用(SPA)变得简单。

核心功能:

  • URL 与组件映射
  • 嵌套路由(子路由)
  • 动态路由(带参数的 URL)
  • 导航守卫(权限控制)
  • 路由懒加载(代码分割)
  • 编程式导航(代码跳转)
    💡 SPA(单页面应用)原理:
    整个应用只有一个 index.html,路由切换时不刷新页面,只是替换 中的组件。
    URL 变化由 History API(pushState/replaceState)控制,浏览器不会向服务器发请求。

三、安装与初始化

第一步:安装 Vue Router

bash 复制代码
npm install vue-router@4

⚠️ 注意:Vue 3 必须用 vue-router@4,Vue 2 用 vue-router@3

第二步:创建路由配置文件

ts 复制代码
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

// 定义路由配置
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
]

// 创建路由器实例
const router = createRouter({
  history: createWebHistory(), // 使用 HTML5 History 模式
  routes
})

export default router

🔍 关键概念解析:

概念 说明
RouteRecordRaw 路由配置的 TS 类型,必须用!
createRouter 创建路由器实例
createWebHistory 使用 HTML5 History 模式(URL 无 #
createWebHashHistory 使用 Hash 模式(URL 有 #,兼容老浏览器)

第三步:在 main.ts 中注册

ts 复制代码
// src/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')

第四步:在 App.vue 中添加路由出口

html 复制代码
<!-- src/App.vue -->
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>

<template>
  <div id="app">
    <!-- 导航链接(相当于 a 标签,但不刷新页面) -->
    <nav>
      <RouterLink to="/">首页</RouterLink>
      <RouterLink to="/about">关于</RouterLink>
    </nav>
    
    <!-- 路由出口(当前匹配的组件会渲染在这里) -->
    <RouterView />
  </div>
</template>

<style>
nav a {
  margin-right: 16px;
  text-decoration: none;
  color: #42b883;
}
nav a.router-link-active {
  font-weight: bold;
  color: #35495e;
}
</style>

✅ 现在访问 http://localhost:5173/http://localhost:5173/about,页面会切换但不刷新!

四、路由传参:三种方式详解

🎯 场景:用户详情页 /users/123

方式一:动态路由参数(:id)

ts 复制代码
// src/router/index.ts
const routes: RouteRecordRaw[] = [
  {
    path: '/users/:id', // 👈 :id 是动态参数
    name: 'UserDetail',
    component: () => import('@/views/UserDetail.vue')
  }
]

导航编写:

html 复制代码
<script setup lang="ts">
import { useCounter } from '@/composables/useCounter'
import { RouterLink, RouterView } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

const { count, isEven, increment, reset } = useCounter(10)

// 获取 store 实例
const userStore = useUserStore()
const { userId } = storeToRefs(userStore)
</script>

<template>
  <div>
    <div id="app">
      <!-- 导航链接(相当于 a 标签,但不刷新页面) -->
      <nav>
        <RouterLink to="/">首页</RouterLink>
        <span></span>
        <RouterLink to="/about">关于</RouterLink>
        <!-- 跳转方式 -->
        <!-- 方式 1:RouterLink -->
        <RouterLink to="/users/123">用户 123</RouterLink>
        <RouterLink :to="{ name: 'UserDetail', params: { id: userId } }"
          >用户 {{ userId }}(命名路由)</RouterLink
        >

        <!-- 方式 2:编程式导航 -->
        <button @click="$router.push('/users/123')">跳转</button>
        <button @click="$router.push({ name: 'UserDetail', params: { id: userId } })">
          跳转(命名)
        </button>
        <button @click="$router.push({ name: 'UserDetail', params: { id: userId } , query: { tab: 'handsome'} })">带query的路由</button>
      </nav>

      <!-- 路由出口(当前匹配的组件会渲染在这里) -->
      <RouterView />
    </div>
  </div>
</template>
<style>
nav a {
  margin-right: 16px;
  text-decoration: none;
  color: #42b883;
}
nav a.router-link-active {
  font-weight: bold;
  color: #35495e;
}
</style>
对比项 字符串路径 to="/users/123" 命名路由 :to="{ name: ... }"
耦合度 高(与 URL 结构绑定) 低(与路由名称绑定)
重构友好 ❌ 改路径要改所有地方 ✅ 只改路由配置,组件不用动
类型检查 ❌ 无 ✅ TS 可检查 name 是否存在
参数传递 手动拼接字符串 自动拼接,更安全
可读性 直观(直接看到 URL) 需查路由表才知道对应路径
推荐场景 简单项目、外部链接 中大型项目、团队协作

无参数时能否用命名路由?

  • ✅ 完全可以!而且推荐这样做
html 复制代码
<!-- 首页,无参数 -->
<RouterLink :to="{ name: 'Home' }">首页</RouterLink>

<!-- 等价于 -->
<RouterLink to="/">首页</RouterLink>

方式二:查询参数(?key=value)

html 复制代码
<!-- App.vue -->
<button @click="$router.push({ name: 'UserDetail', params: { id: userId } , query: { tab: 'handsome'} })">
带query的路由
</button>
html 复制代码
<!-- src/views/UserDetail.vue -->
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { computed } from 'vue'

const route = useRoute()

// 获取动态参数(类型是 string | string[])
const userId = computed(() => route.params.id as string)

// 获取查询参数 ?tab=profile
const tab = computed(() => route.query.tab as string)
</script>

<template>
  <div>
    <h2>用户详情</h2>
    <p>用户 ID: {{ userId }}</p>
    <p>当前标签:{{ tab || '默认' }}</p>
  </div>
</template>

方式三:路由元信息(meta)

ts 复制代码
// src/router/index.ts
const routes: RouteRecordRaw[] = [
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('@/views/Admin.vue'),
    meta: {
      requiresAuth: true, // 需要登录
      roles: ['admin']    // 需要管理员角色
    }
  }
]
html 复制代码
<!-- src/views/Admin.vue -->
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()

const requiresAuth = route.meta.requiresAuth
const roles = route.meta.roles as string[]
</script>

<template>
  <div>是否需要管理员身份: {{ requiresAuth }}</div>
  <div>当前用户角色: {{ roles }}</div>
</template>

<!-- 在nav中添加 -->
<RouterLink :to="{ name: 'Admin' }">管理员</RouterLink>

完整的路由位置对象

ts 复制代码
// 完整的路由位置对象
{
  name: 'RouteName',      // 路由名称(二选一:name 或 path)
  path: '/some-path',     // 路由路径
  params: { id: 123 },    // 动态参数(对应 :id)
  query: { k: 'v' },      // 查询参数(对应 ?k=v)
  hash: '#section',       // 哈希锚点
  replace: false          // 是否 replace 模式(不记录历史)
}

五、嵌套路由:父子组件路由

🎯 场景:后台管理系统布局

text 复制代码
/layout
  ├── /dashboard   (仪表盘)
  ├── /users       (用户管理)
  └── /settings    (设置)

配置嵌套路由

ts 复制代码
// src/router/index.ts
const routes: RouteRecordRaw[] = [
  {
    path: '/layout',
    component: () => import('@/views/Layout.vue'),
    children: [
      {
        path: '', // 默认子路由
        redirect: '/layout/dashboard'
      },
      {
        path: 'dashboard', // 注意:不要加 /
        name: 'Dashboard',
        component: () => import('@/views/Dashboard.vue')
      },
      {
        path: 'users',
        name: 'Users',
        component: () => import('@/views/Users.vue')
      },
      {
        path: 'settings',
        name: 'Settings',
        component: () => import('@/views/Settings.vue')
      }
    ]
  }
]

父组件:Layout.vue

html 复制代码
<!-- src/views/Layout.vue -->
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>

<template>
  <div class="layout">
    <aside class="sidebar">
      <RouterLink to="/layout/dashboard">仪表盘</RouterLink>
      <RouterLink to="/layout/users">用户管理</RouterLink>
      <RouterLink to="/layout/settings">设置</RouterLink>
    </aside>
    
    <main class="content">
      <!-- 子路由出口 -->
      <RouterView />
    </main>
  </div>
</template>

<style scoped>
.layout {
  display: flex;
  height: 100vh;
}
.sidebar {
  width: 200px;
  background: #f5f5f5;
  padding: 20px;
}
.sidebar a {
  display: block;
  margin: 10px 0;
  text-decoration: none;
  color: #333;
}
.content {
  flex: 1;
  padding: 20px;
}
</style>

⚠️ 注意:

  • 子路由的 path 不要加前导 /,否则会变成根路径
  • 父组件必须有 <RouterView />,子路由才能渲染

六、导航守卫:权限控制的核心

🔎 先认识导航守卫(Navigation Guards)

导航守卫是什么?

它是 Vue Router 提供的路由拦截机制,在路由跳转前/后执行特定逻辑。

典型用途:

  • 检查用户是否登录
  • 检查用户是否有权限
  • 页面加载前预取数据
  • 离开页面时确认是否保存

三种守卫类型:

类型 作用域 使用场景
全局守卫 所有路由 登录检查、权限验证
路由独享守卫 单个路由 特定页面的前置逻辑
组件内守卫 单个组件 组件级别的拦截

✅ 实战:全局前置守卫(登录检查)

ts 复制代码
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { useUserStore } from '@/stores/user'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue')
  },
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('@/views/Admin.vue'),
    meta: { requiresAuth: true } // 需要登录
  }
]

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

// ========== 全局前置守卫 ==========
router.beforeEach((to, from, next) => {
  const userStore = useUserStore()
  
  // 检查是否需要登录
  if (to.meta.requiresAuth && !userStore.isLoggedIn) {
    // 未登录,重定向到登录页
    next({
      name: 'Login',
      query: { redirect: to.fullPath } // 保存原目标,登录后跳回
    })
  } else {
    // 可以通行
    next()
  }
})

export default router

🔍 守卫参数解析:

参数 类型 说明
to RouteLocationNormalized 即将进入的目标路由
from RouteLocationNormalized 当前离开的路由
next Function 必须调用,否则路由不会跳转

next() 的用法:

ts 复制代码
next()              // 继续跳转
next(false)         // 取消跳转
next('/login')      // 重定向到新路径
next({ name: 'Home' }) // 重定向到命名路由
next(error)         // 触发错误处理

✅ 实战:登录后跳至目标页面

html 复制代码
<!-- src/views/Login.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'

const route = useRoute()
const router = useRouter()
const userStore = useUserStore()

const email = ref('')
const password = ref('')

async function handleLogin() {
  const success = await userStore.login(email.value, password.value)
  
  if (success) {
    // 获取 redirect 参数,没有则跳首页
    const redirect = route.query.redirect as string
    router.push(redirect || '/')
  }
}
</script>

<template>
  <div>
    <h2>登录</h2>
    <input v-model="email" placeholder="邮箱" />
    <input v-model="password" type="password" placeholder="密码" />
    <button @click="handleLogin">登录</button>
  </div>
</template>

七、路由懒加载:代码分割优化

🎯 问题:所有组件打包成一个文件,首屏加载慢

✅ 解决方案:动态导入(import()

ts 复制代码
// ❌ 不好:所有组件一起加载
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

// ✅ 好:按需加载,每个组件独立 chunk
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    component: () => import('@/views/Home.vue') // 👈 动态导入
  },
  {
    path: '/about',
    component: () => import('@/views/About.vue')
  }
]

💡 原理:

Vite/Rollup 会将每个 import() 打包成独立的 .js 文件,只有访问该路由时才下载。

八、与 Pinia 结合:动态权限菜单

🎯 场景:不同角色看到不同菜单

ts 复制代码
// src/router/index.ts
const routes: RouteRecordRaw[] = [
  {
    path: '/admin',
    meta: { roles: ['admin'] }, // 只有管理员能访问
    component: () => import('@/views/Admin.vue')
  },
  {
    path: '/user',
    meta: { roles: ['user', 'admin'] }, // 用户和管理员都能访问
    component: () => import('@/views/User.vue')
  }
]

菜单组件中

html 复制代码
<!-- src/components/Menu.vue -->
<script setup lang="ts">
import { computed } from 'vue'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// 定义菜单配置
const menuItems = [
  { path: '/dashboard', label: '仪表盘', roles: ['user', 'admin'] },
  { path: '/admin', label: '管理后台', roles: ['admin'] },
  { path: '/settings', label: '设置', roles: ['user', 'admin'] }
]

// 根据角色过滤菜单
const visibleMenu = computed(() => {
  const userRole = userStore.user?.role || 'user'
  return menuItems.filter(item => item.roles.includes(userRole))
})
</script>

<template>
  <nav>
    <RouterLink 
      v-for="item in visibleMenu" 
      :key="item.path"
      :to="item.path"
    >
      {{ item.label }}
    </RouterLink>
  </nav>
</template>

九、常见误区与最佳实践

误区 正确做法
setup() 顶层用 useRouter ✅ 可以在 setup() 中使用
忘记调用 next() 守卫中必须调用 next(),否则路由卡住
子路由 path 加 / 子路由 path 不要加前导 /
所有路由都懒加载 首屏关键组件可同步加载,次要页面懒加载
权限只在前端控制 前端控制 UI,后端必须验证 API 权限

✅ 路由配置最佳实践:

ts 复制代码
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

// 1. 按模块拆分路由(大型项目)
const publicRoutes: RouteRecordRaw[] = [...]
const adminRoutes: RouteRecordRaw[] = [...]

// 2. 统一导出
export const routes: RouteRecordRaw[] = [...publicRoutes, ...adminRoutes]

// 3. 导出 router 实例(便于测试)
export const router = createRouter({
  history: createWebHistory(),
  routes
})

// 4. 默认导出(便于 main.ts 导入)
export default router

✅ 本篇小结

概念 说明
createRouter 创建路由器实例
RouteRecordRaw 路由配置的 TS 类型
useRoute 获取当前路由信息(组件内)
useRouter 获取路由器实例(用于跳转)
beforeEach 全局前置守卫(权限检查)
meta 路由元信息(存储自定义数据)
动态导入 () => import() 实现懒加载

📘 补充讲解:useRoute vs useRouter 的区别

一句话总结

API 作用 类比
useRoute() 读当前路由信息 像看地图------你现在在哪?
useRouter() 操作路由跳转 像开车------你要去哪?
对比项 useRoute() useRouter()
用途 获取当前路由信息 执行导航操作
返回值 RouteLocationNormalizedLoaded Router 实例
常见操作 paramsquerypathname push()replace()back()
响应式 ✅ 是(路由变化时自动更新) ❌ 否(路由器实例不变)
是否需要导入 import { useRoute } from 'vue-router' import { useRouter } from 'vue-router'

📍 useRoute() ------ 读取当前路由信息

html 复制代码
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { computed } from 'vue'

// 获取当前路由对象(响应式!)
const route = useRoute()

// 读取动态参数 /users/123 → 123
const userId = computed(() => route.params.id as string)

// 读取查询参数 /search?keyword=iphone → 'iphone'
const keyword = computed(() => route.query.keyword as string)

// 读取当前路径
const currentPath = computed(() => route.path) // '/users/123'

// 读取路由名称
const currentName = computed(() => route.name) // 'UserDetail'

// 读取 meta 信息
const requiresAuth = computed(() => route.meta.requiresAuth)
</script>

✅ 关键点:route 是响应式的!

当 URL 变化时(比如用户从 /users/1 跳到 /users/2),route.params.id 会自动更新,模板也会重新渲染。

🚗 useRouter() ------ 执行路由跳转

html 复制代码
<script setup lang="ts">
import { useRouter } from 'vue-router'

// 获取路由器实例(非响应式)
const router = useRouter()

// 跳转到新页面(会记录历史)
function goToHome() {
  router.push('/')
}

// 跳转到新页面(不记录历史)
function goToLogin() {
  router.replace('/login')
}

// 后退一页
function goBack() {
  router.back()
}

// 前进一页
function goForward() {
  router.forward()
}

// 跳转到命名路由
function goToUser(id: number) {
  router.push({ name: 'UserDetail', params: { id } })
}

// 带查询参数跳转
function search(keyword: string) {
  router.push({ path: '/search', query: { keyword } })
}
</script>

✅ 关键点:router 是单例对象,整个应用只有一个,不会随路由变化而改变。

特性 useRoute() useRouter()
核心用途 读取当前路由信息 执行路由导航操作
返回类型 RouteLocationNormalizedLoaded Router
响应式 ✅ 是 ❌ 否
常用属性/方法 params, query, path, name, meta push(), replace(), back(), forward()
典型场景 获取 URL 参数、读取 meta 信息 页面跳转、编程式导航
记忆技巧 Route = 当前路线 Router = 路由器
相关推荐
木子欢儿1 小时前
在 Debian 13(以及 12)上安装和配置 tightvncserver 并让普通用户使
运维·前端·debian
SakitamaX2 小时前
Nginx安装与实验
服务器·前端·nginx
用户新2 小时前
V8引擎 精品漫游指南--Ignition篇(中) AST详解 字节码的生成
前端·javascript
岱宗夫up2 小时前
【前端基础】HTML + CSS + JavaScript 基础(三)
开发语言·前端·javascript·css·html
凌云拓界3 小时前
TypeWell全攻略:AI健康教练+实时热力图开发实战 引言
前端·人工智能·后端·python·交互·pyqt·数据可视化
明月_清风3 小时前
三件套快速上手 + 第一个可安装的 PWA(HTTPS + Manifest + 基础 Service Worker)
前端·pwa
菜鸟小芯3 小时前
【GLM-5 陪练式前端新手入门】第三篇:网页导航栏 —— 搭建个人主页的 “指路牌”
前端
明月_清风3 小时前
PWA 到底是什么?它在 2026 年解决了哪些真实痛点?
前端·pwa
甲枫叶3 小时前
【claude产品经理系列13】核心功能实现——需求的增删改查全流程
java·前端·人工智能·python·产品经理·ai编程