【Vue2手录13】路由Vue Router

发布于:2025-09-15 ⋅ 阅读:(11) ⋅ 点赞:(0)

一、Vue Router 基础概念与核心原理

1.1 路由本质与核心要素

  • 本质定义:路由是URL路径页面组件的对应关系,通过路径变化控制视图切换,实现单页应用(SPA)的无刷新页面切换。
  • 核心三要素
    1. router-link:声明式导航组件,替代原生<a>标签(避免页面刷新),通过to属性指定目标路径。
    2. router-view:路由视图出口,匹配路径的组件会渲染到该标签位置(路由有几级嵌套,就需要几个router-view)。
    3. 路由实例:通过new VueRouter()创建,配置路径与组件的映射规则(routes数组)。

1.2 路由模式对比(hash vs history)

路由模式决定URL的表现形式和底层实现逻辑,是面试高频考点,需重点区分:

对比维度 hash模式(默认) history模式(推荐)
URL表现 #号(如http://localhost:8080/#/home #号(如http://localhost:8080/home
实现原理 基于window.onhashchange事件监听#后路径变化 基于HTML5 History APIpushState/popState
后端依赖 纯前端实现,#后路径不发送到后端,无需配置 路径会完整发送到后端,刷新可能触发404
404问题解决 无此问题 需后端配置(如Nginx):未匹配路径返回index.html
适用场景 简单Demo、无需美观URL的项目 生产环境、对URL美观度和SEO有要求的项目
后端Nginx配置示例(解决history模式404)
location / {
  root   /usr/share/nginx/html; # 项目打包后的目录
  index  index.html index.htm;
  try_files $uri $uri/ /index.html; # 核心配置:未匹配路径返回index.html
}

1.3 路由注册机制

  • Vue.use(VueRouter)的必要性
    Vue Router是Vue生态专属插件,需通过Vue.use()显式注册,否则路由功能无法生效(类比“插座与插头”,必须配套使用)。
    注意:通用工具库(如Axios)无需此步骤,仅Vue专属插件(Vue Router、Vuex)需注册。
  • 注册原理Vue.use()会调用插件内部的install方法,将路由实例注入Vue全局,使所有组件可通过this.$router访问路由实例。

二、路由核心配置流程(Vue 2 + Vue Router 3.x)

路由配置需遵循“模块化”原则,避免与main.js混杂,标准流程如下:

2.1 标准配置步骤(五步走)

步骤1:创建路由目录与文件

src下新建router文件夹,创建index.js(路由配置主文件,Webpack默认优先查找index.js)。

步骤2:引入依赖并注册插件
// src/router/index.js
import Vue from 'vue' // 引入Vue核心
import VueRouter from 'vue-router' // 引入Vue Router
Vue.use(VueRouter) // 注册路由插件(必须步骤)
步骤3:定义路由规则(routes数组)

routes是路由配置的核心,每个路由对象包含path(路径)、component(对应组件)等属性:

// 1. 引入组件(支持懒加载,优化首屏性能)
const Home = () => import('@/views/Home.vue') // 懒加载写法
const About = () => import('@/views/About.vue')
const User = () => import('@/views/User.vue')

// 2. 定义路由规则
const routes = [
  {
    path: '/', // 默认路径
    redirect: '/home' // 重定向到首页(避免空白页面)
  },
  {
    path: '/home',
    name: 'Home', // 路由名称(可选,用于命名路由跳转)
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  },
  {
    path: '/user/:id', // 动态路由::id为动态参数
    name: 'User',
    component: User
  }
]
步骤4:创建路由实例并导出
const router = new VueRouter({
  mode: 'history', // 路由模式:hash/history(默认hash)
  routes // 注入路由规则(ES6简写,等同于routes: routes)
})

export default router // 导出路由实例,供main.js引入
步骤5:注入根实例并添加router-view
  1. main.js中引入路由实例:
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router' // 引入路由实例(自动找router/index.js)

new Vue({
  router, // 注入路由实例(使所有组件可访问$router/$route)
  render: h => h(App)
}).$mount('#app')
  1. App.vue中添加router-view(指定组件渲染位置):
<!-- src/App.vue -->
<template>
  <div id="app">
    <!-- 1. 路由导航 -->
    <nav>
      <router-link to="/home">首页</router-link>
      <router-link to="/about">关于我们</router-link>
    </nav>
    <!-- 2. 路由视图出口:匹配的组件会渲染到这里 -->
    <router-view></router-view>
  </div>
</template>

2.2 routes数组核心属性详解

属性 作用与说明 示例
path 路由路径(必须以/开头,子路由可省略/自动拼接父路径) /home(一级路由)、child(子路由)
component 路径对应的组件(支持懒加载() => import()和直接引入) Home() => import('@/views/Home.vue')
name 路由名称(唯一,用于命名路由跳转,解耦路径变化) name: 'Home'
children 子路由配置数组(实现嵌套路由,每个子路由结构与父路由一致) children: [{ path: 'child', component: Child }]
redirect 重定向(访问当前路径时自动跳转到目标路径,支持字符串/name对象) redirect: '/home'redirect: { name: 'Home' }
alias 路由别名(给路由多个访问路径,地址栏显示别名,组件不变) alias: '/index'(访问/index等同于/home

三、路由进阶特性(实战必备)

3.1 嵌套路由(多级路由)

嵌套路由用于实现“父组件包含子组件”的层级结构(如后台管理系统的“侧边栏+内容区”),核心是children配置和多级router-view

1. 配置示例(后台管理系统)
// src/router/index.js
const Layout = () => import('@/views/Layout.vue') // 父布局组件
const Dashboard = () => import('@/views/Dashboard.vue') // 子组件1
const Settings = () => import('@/views/Settings.vue') // 子组件2

const routes = [
  {
    path: '/admin',
    name: 'Layout',
    component: Layout,
    // 子路由配置:path不加/,自动拼接父路径(/admin/dashboard)
    children: [
      {
        path: '', // 子路由默认路径(访问/admin时渲染Dashboard)
        component: Dashboard
      },
      {
        path: 'settings', // 子路由路径:完整路径为/admin/settings
        name: 'Settings',
        component: Settings
      }
    ]
  }
]
2. 父组件(Layout.vue)添加子路由视图
<!-- src/views/Layout.vue -->
<template>
  <div class="layout">
    <!-- 侧边栏(固定父组件内容) -->
    <aside>
      <router-link :to="{ name: 'Dashboard' }">数据看板</router-link>
      <router-link :to="{ name: 'Settings' }">系统设置</router-link>
    </aside>
    <!-- 子路由视图出口:子组件(Dashboard/Settings)渲染到这里 -->
    <main>
      <router-view></router-view>
    </main>
  </div>
</template>

关键注意点
  • 子路由path不加/:自动拼接父路由路径(如settings/admin/settings);加/则为绝对路径(需写完整路径,如/admin/settings)。
  • 每级路由需对应router-view:父路由组件放父级router-view,子路由组件放子级router-view(路由有几级,就需要几个router-view)。

3.2 动态路由(路径参数)

动态路由用于“多个路径对应同一个组件”的场景(如用户详情页/user/1/user/2均渲染User组件),核心是通过:声明动态参数。

1. 配置示例(用户详情页)
// src/router/index.js
const User = () => import('@/views/User.vue')

const routes = [
  {
    // :id为动态参数,可自定义名称(如:userId)
    path: '/user/:id', 
    name: 'User',
    component: User,
    // 可选:通过props将params参数注入组件(避免在组件中写$route.params)
    props: true 
  }
]
2. 组件中获取动态参数
<!-- src/views/User.vue -->
<template>
  <div>
    <!-- 方式1:直接通过$route.params获取 -->
    <h1>用户ID:{{ $route.params.id }}</h1>
    <!-- 方式2:通过props接收(需路由配置props: true) -->
    <h1>用户ID(props):{{ id }}</h1>
  </div>
</template>
<script>
export default {
  name: 'User',
  props: ['id'], // 接收路由注入的params参数
  // 监听参数变化(组件复用场景,如从/user/1跳转到/user/2)
  watch: {
    $route(to, from) {
      console.log('用户ID变化:', to.params.id)
    }
  }
}
</script>

3. 动态路由跳转方式
跳转方式 代码示例 说明
路径字符串 this.$router.push('/user/123') 简单直接,适合固定路径
path对象 this.$router.push({ path: '/user/123' }) 路径需完整拼接,不支持单独传params
name+params this.$router.push({ name: 'User', params: { id: 123 } }) 推荐:解耦路径,params自动拼接路径

3.3 查询参数(query)

查询参数用于“非路径必需的临时数据传递”(如列表筛选、分页),格式为URL?key=value&key2=value2,无需预配置路由。

1. 跳转与参数获取示例
<!-- 跳转组件(如Home.vue) -->
<template>
  <button @click="goProductList">查看商品列表</button>
</template>
<script>
export default {
  methods: {
    goProductList() {
      // 方式1:path对象+query
      this.$router.push({
        path: '/product',
        query: { category: 'phone', page: 1 } // 查询参数
      })
      // 方式2:name对象+query(推荐,解耦路径)
      // this.$router.push({
      //   name: 'Product',
      //   query: { category: 'phone', page: 1 }
      // })
    }
  }
}
</script>
<!-- 目标组件(Product.vue)获取参数 -->
<template>
  <div>
    <h1>筛选分类:{{ $route.query.category }}</h1>
    <h1>当前页码:{{ $route.query.page }}</h1>
  </div>
</template>

2. 动态路由 vs 查询参数(核心区别)
对比维度 动态路由(params) 查询参数(query)
路径表现 是路径的一部分(/user/123 附加在URL后(/product?category=phone
路由配置 需预定义动态参数(/user/:id 无需预配置
参数必要性 必选(不传递参数会导致路由匹配失败) 可选(不传递也能匹配路由)
数据用途 标识资源(如用户ID、商品ID) 筛选、排序、分页等临时数据
参数获取 $route.params $route.query

3.4 通配符与404页面

通配符*用于匹配所有未明确定义的路由,常用来实现404页面(提升用户体验,替代空白页面)。

1. 配置示例
// src/router/index.js
const NotFound = () => import('@/views/NotFound.vue') // 404组件

const routes = [
  // 其他路由配置...
  // 通配符路由:必须放在最后(确保优先匹配明确路由)
  {
    path: '*',
    component: NotFound
  }
]
2. 404组件示例(NotFound.vue
<template>
  <div class="not-found">
    <h1>404 - 页面走丢了!</h1>
    <p>您访问的路径:{{ $route.params.pathMatch }}</p> <!-- 获取无效路径 -->
    <router-link to="/home">返回首页</router-link>
  </div>
</template>
<style scoped>
.not-found {
  text-align: center;
  margin-top: 50px;
  color: #666;
}
</style>

关键注意点
  • 通配符位置:必须放在路由配置最后(旧版Vue Router 3.x严格要求,新版虽优化,但保持习惯可兼容)。
  • pathMatch参数:通配符匹配时,$route.params.pathMatch会自动包含用户访问的无效路径,可用于页面提示。

3.5 重定向与别名

1. 重定向(redirect)

重定向是“访问A路径时自动跳转到B路径”,地址栏会显示B路径的URL,适合旧路径兼容、默认页面跳转。

重定向方式 代码示例 适用场景
字符串路径 redirect: '/home' 简单固定跳转
name对象 redirect: { name: 'Home' } 依赖路由名称,路径变化无需修改
动态函数 redirect: to => '/home' 复杂逻辑(如根据参数动态跳转)
2. 别名(alias)

别名是“给路由多个访问路径”,访问别名路径时,组件不变且地址栏显示别名,适合隐藏真实路由结构、兼容旧URL。

const routes = [
  {
    path: '/home',
    name: 'Home',
    component: Home,
    alias: '/index' // 访问/index等同于访问/home,地址栏显示/index
  }
]
重定向 vs 别名(核心区别)
  • 重定向:URL会变化(A→B),本质是“跳转”;
  • 别名:URL不变(访问别名路径),本质是“同一资源的多个入口”。

四、补充重点知识(性能与实战技巧)

4.1 路由懒加载(性能优化)

路由懒加载通过“按需加载组件”减少首屏加载时间,核心是component: () => import('组件路径')语法(Webpack动态导入)。

1. 配置示例
// 懒加载写法(推荐)
const Home = () => import('@/views/Home.vue')
// 对比:普通导入(首屏会加载所有组件,性能差)
// import Home from '@/views/Home.vue'

const routes = [
  { path: '/home', component: Home }
]
2. 优势
  • 首屏加载体积减小:仅加载当前路径的组件,而非所有组件;
  • 提升首屏渲染速度:避免因组件过多导致的白屏时间过长。

4.2 路径别名@的使用

@是Webpack默认配置的路径别名,代表src目录,可避免复杂的相对路径(如../../views/Home.vue)。

1. 使用示例
// 普通相对路径(繁琐,易出错)
import Home from '../../views/Home.vue'
// @别名路径(简洁,项目结构变化无需修改)
import Home from '@/views/Home.vue'
2. 原理

Webpack配置中通过resolve.alias定义:

// webpack.config.js(Vue CLI项目无需手动配置)
module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src') // @指向src目录
    }
  }
}

4.3 路由守卫基础(权限控制)

路由守卫用于“路由跳转前/后拦截”,常实现登录验证、权限判断等功能,核心介绍全局前置守卫beforeEach

登录验证示例
// src/router/index.js
router.beforeEach((to, from, next) => {
  // 1. 不需要登录的页面(如首页、登录页)直接放行
  const whiteList = ['/home', '/login']
  if (whiteList.includes(to.path)) {
    return next()
  }
  // 2. 需要登录:判断是否有token
  const token = localStorage.getItem('token')
  if (token) {
    next() // 有token,放行
  } else {
    next('/login') // 无token,跳转到登录页
  }
})
  • to:即将进入的目标路由对象;
  • from:当前离开的路由对象;
  • next:必须调用的函数,控制是否放行(next()放行,next('/login')跳转到指定路径)。

五、知识小结

知识点 核心内容 考试重点/易混淆点 难度系数
路由模式 hash(带#,纯前端)、history(无#,需后端配置) history模式404问题与Nginx配置 ⭐⭐⭐
嵌套路由 children配置子路由,多级router-view对应层级 子路由路径加不加/的区别;router-view数量与路由层级匹配 ⭐⭐⭐
动态路由 :param声明参数,$route.params获取,组件复用需监听$route 动态路由跳转的三种方式;props: true注入参数的用法 ⭐⭐⭐⭐
查询参数 URL?key=value$route.query获取,无需预配置路由 与动态路由的区别(必选vs可选、路径部分vs附加部分) ⭐⭐⭐
通配符与404 *匹配所有路径,必须放路由最后,pathMatch获取无效路径 通配符位置的影响;404页面的用户体验设计 ⭐⭐
重定向与别名 重定向(URL变化)、别名(URL不变) 两者的本质区别;别名的实际应用场景 ⭐⭐
路由懒加载 component: () => import()按需加载,优化首屏性能 与普通导入的区别;懒加载对打包体积的影响 ⭐⭐⭐
路由守卫 beforeEach全局拦截,实现登录验证、权限控制 next()函数的必选性;白名单的设计思路 ⭐⭐⭐⭐

六、实战小练习

6.1 题目

  1. 动态路由实战:实现“商品详情页”,要求:
    • 配置动态路由/product/:productId,对应ProductDetail组件;
    • Home组件中通过router-link和编程式导航两种方式跳转到详情页(传递productId=1001);
    • ProductDetail组件中显示商品ID,并监听ID变化(从1001跳转到1002时打印新ID)。
  2. 查询参数实战:实现“商品列表筛选”,要求:
    • 配置路由/product-list,对应ProductList组件;
    • Home组件中跳转时传递查询参数category=phone&sort=price
    • ProductList组件中显示筛选分类和排序方式,并支持通过按钮切换排序(sort=pricesort=sales)。
  3. 404与重定向实战
    • 创建NotFound组件,配置通配符路由实现404页面;
    • 配置重定向:访问/时跳转到/home,访问/old-home时跳转到/home(通过name对象)。
  4. 嵌套路由实战:实现“个人中心”嵌套结构,要求:
    • 父路由/profile对应ProfileLayout组件(包含“基本信息”“我的订单”导航);
    • 子路由/profile/basic对应ProfileBasic组件,/profile/orders对应ProfileOrders组件;
    • 访问/profile时默认显示ProfileBasic组件。

6.2 参考答案

1. 动态路由实战
(1)路由配置(src/router/index.js
const Home = () => import('@/views/Home.vue')
const ProductDetail = () => import('@/views/ProductDetail.vue')

const routes = [
  { path: '/home', name: 'Home', component: Home },
  { 
    path: '/product/:productId', 
    name: 'ProductDetail', 
    component: ProductDetail,
    props: true // 注入productId到props
  }
]
(2)跳转组件(Home.vue
<template>
  <div>
    <h1>首页</h1>
    <!-- 方式1:router-link跳转 -->
    <router-link :to="{ name: 'ProductDetail', params: { productId: 1001 } }">
      查看商品1001(router-link)
    </router-link>
    <!-- 方式2:编程式导航 -->
    <button @click="goToProduct(1001)">查看商品1001(编程式)</button>
  </div>
</template>
<script>
export default {
  methods: {
    goToProduct(id) {
      this.$router.push({ name: 'ProductDetail', params: { productId: id } })
    }
  }
}
</script>

(3)详情组件(ProductDetail.vue
<template>
  <div>
    <h1>商品详情页</h1>
    <p>商品ID:{{ productId }}</p>
    <button @click="goToNextProduct">切换到商品1002</button>
  </div>
</template>
<script>
export default {
  name: 'ProductDetail',
  props: ['productId'], // 接收路由注入的参数
  methods: {
    goToNextProduct() {
      this.$router.push({ name: 'ProductDetail', params: { productId: 1002 } })
    }
  },
  // 监听参数变化
  watch: {
    productId(newId, oldId) {
      console.log(`商品ID从${oldId}变为${newId}`)
    }
  }
}
</script>

2. 查询参数实战
(1)路由配置(src/router/index.js
const ProductList = () => import('@/views/ProductList.vue')

const routes = [
  { path: '/product-list', name: 'ProductList', component: ProductList }
]
(2)跳转组件(Home.vue
<template>
  <button @click="goToProductList">查看手机商品列表</button>
</template>
<script>
export default {
  methods: {
    goToProductList() {
      this.$router.push({
        name: 'ProductList',
        query: { category: 'phone', sort: 'price' }
      })
    }
  }
}
</script>

(3)列表组件(ProductList.vue
<template>
  <div>
    <h1>商品列表</h1>
    <p>筛选分类:{{ $route.query.category }}</p>
    <p>排序方式:{{ $route.query.sort }}</p>
    <button @click="changeSort">切换排序为“销量”</button>
  </div>
</template>
<script>
export default {
  methods: {
    changeSort() {
      // 保留当前category参数,仅修改sort
      this.$router.push({
        name: 'ProductList',
        query: { ...this.$route.query, sort: 'sales' }
      })
    }
  }
}
</script>

3. 404与重定向实战
(1)404组件(NotFound.vue
<template>
  <div style="text-align: center; margin-top: 50px;">
    <h1 style="color: #f44336;">404 - 页面不存在</h1>
    <p>您访问的路径:{{ $route.params.pathMatch }}</p>
    <router-link to="/home" style="color: #2196f3;">返回首页</router-link>
  </div>
</template>

(2)路由配置(src/router/index.js
const NotFound = () => import('@/views/NotFound.vue')

const routes = [
  // 重定向:/ → /home,/old-home → /home
  { path: '/', redirect: '/home' },
  { path: '/old-home', redirect: { name: 'Home' } },
  { path: '/home', name: 'Home', component: Home },
  // 通配符路由放最后
  { path: '*', component: NotFound }
]
4. 嵌套路由实战
(1)路由配置(src/router/index.js
const ProfileLayout = () => import('@/views/ProfileLayout.vue')
const ProfileBasic = () => import('@/views/ProfileBasic.vue')
const ProfileOrders = () => import('@/views/ProfileOrders.vue')

const routes = [
  {
    path: '/profile',
    name: 'ProfileLayout',
    component: ProfileLayout,
    children: [
      { path: '', component: ProfileBasic }, // 默认显示基本信息
      { path: 'basic', name: 'ProfileBasic', component: ProfileBasic },
      { path: 'orders', name: 'ProfileOrders', component: ProfileOrders }
    ]
  }
]
(2)父布局组件(ProfileLayout.vue
<template>
  <div class="profile-layout">
    <!-- 侧边导航 -->
    <aside>
      <router-link :to="{ name: 'ProfileBasic' }">基本信息</router-link>
      <router-link :to="{ name: 'ProfileOrders' }">我的订单</router-link>
    </aside>
    <!-- 子路由视图出口 -->
    <main>
      <router-view></router-view>
    </main>
  </div>
</template>
<style scoped>
.profile-layout {
  display: flex;
}
aside {
  width: 200px;
  border-right: 1px solid #eee;
  padding: 20px;
}
aside router-link {
  display: block;
  margin: 10px 0;
  text-decoration: none;
}
main {
  flex: 1;
  padding: 20px;
}
</style>

(3)子组件(ProfileBasic.vue示例)
<template>
  <div>
    <h2>基本信息</h2>
    <p>用户名:张三</p>
    <p>手机号:138****1234</p>
  </div>
</template>