vue-router 是 Vue 官方的路由管理器,用于实现单页面应用(SPA)的路由跳转 —— 简单说,就是让页面 URL 变化时,不刷新整个页面,只替换页面中的指定内容,实现 “无刷新跳转”。
如果刚开始没有选择vue-router,需要npm i下载指定的vue-router版本
npm i vue-router@4.5.0
在src的rourer目录下创建index.ts
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue'),
},
],
})
export default router
在main.ts里面使用
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
简单使用
<template>
<div>
<div class="nav">
<router-link to="/">home</router-link>
<router-link to="/about">about</router-link>
</div>
<div class="view">
<router-view></router-view>
</div>
</div>
</template>
这里引入了两个组件,router-view就是访问对应路由的时候,对应的视图文件就会被替换到这里来
router-link就是一个特殊的a标签,取消了默认跳转,并且加上了一些特定的class样式
路由规则routes结构
import type {RouteRecordRaw} from "vue-router";
const routes: Readonly<RouteRecordRaw[]> = [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue'),
},
]
子路由和从属关系
如果有子路由,把它放到children里面
要特别明白路由的从属关系以及路由的层级关系
const routes: Readonly<RouteRecordRaw[]> = [
{
path: '/', // 生效的路径
name: 'home', // 路由的name
component: HomeView, // 路由对应的视图组件,
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
{
path: "/admin",
name: "admin",
component: AdminView,
children: [
{
path: "", // /admin
name: "adminHome",
component: AdminHome,
},
{
path: "info", // /admin/info
name: "infoHome",
component: infoHome,
}
]
},
{
path: "/admin1",
name: "admin1",
children: [
{
path: "", // /admin
name: "adminHome1",
component: AdminHome,
},
{
path: "info", // /admin/info
name: "infoHome1",
component: infoHome,
}
]
}
]
其中adminHome1和infoHome1都是一级路由,和about、home都是同一级别的路由,替换router-view也只替换一个,但是这两个路由(adminHome1和infoHome1)是从属与admin1的,此时admin1只是一个路由分组
其中adminHome和infoHome是二级路由,路由从属与admin,在替换adminHome和infoHome的视图文件的时候,就得替换两个router-view
路由跳转
- 使用router-link跳转
<div class="nav">
<router-link to="/">home</router-link>
<router-link to="/about">about</router-link>
</div>
<div class="nav">
<router-link :to="{name: 'home'}">home</router-link>
<router-link :to="{name: 'about'}">about</router-link>
</div>
- 使用router对象跳转
<div class="nav">
<a href="javascript:void 0" @click="goRouter1('home')">home</a>
<a href="javascript:void 0" @click="goRouter2('/about')">about</a>
<a href="javascript:void 0" @click="goRouter3('adminHome')">admin Home</a>
</div>
<script lang="ts" setup>
import router from "@/router";
import {useRouter} from "vue-router";
const router1 = useRouter()
// 用路由名称跳
function goRouter1(name: string){
router.push({name: name})
}
// 用路径跳转
function goRouter2(path: string){
router.push(path)
}
// 使用useRouter实例跳
function goRouter3(name: string){
router1.push({name: name})
}
</script>
两种路由模式
- history模式 createWebHistory
/xxx
/admin
/admin/user
如果没有正确的配置nginx,在部署之后有可能会出现404
- hash模式 createWebHashHistory
/#/admin1
/#/
/#/admin1/home
路由参数
- 查询参数
?name=xxx&age=22
import {useRoute} from "vue-router";
const route = useRoute()
console.log(route.query) // 查询参数
需要在对应的视图文件里面去写
- useRoute和useRouter的区别
useRoute是路由信息,一般用于获取当前视图的路由信息
useRouter是路由器对象,一般用于路由操作
- 动态路径参数
/article/1 /article/2 /article/:id
定义动态路径
{
path: "/article/:id",
name: "articleDetail",
component: articleDetail,
}
路由跳转的时候,需要传这个动态参数
<router-link :to="{name: 'articleDetail', params: {id: 1}}">article 1</router-link>
<router-link :to="{name: 'articleDetail', params: {id: 2}}">article 2</router-link>
<router-link :to="{name: 'articleDetail', params: {id: 3}}">article 3</router-link>
在视图中获取这个值
<script setup lang="ts">
import {useRoute} from "vue-router";
import {watch} from "vue";
const route = useRoute()
watch(()=>route.params, ()=>{
console.log("路由路径发生了变化")
})
</script>
<template>
<div>article detail {{ route.params.id }}</div>
</template>
还可以通过watch监听路由的变化
路由懒加载
之前我们定义的视图文件
import HomeView from '../views/HomeView.vue'
import AdminHome from '../views/admin/home.vue'
import infoHome from '../views/admin/info.vue'
import AdminView from '../views/admin/index.vue'
import articleDetail from '../views/article_detail.vue'
都是直接在index.ts的上方直接import导入的
会有一个问题,在打包的时候,这些视图文件都会被打进一个js文件里面,就会造成这个文件特别大
首次加载就会很久,造成页面白屏
使用import()动态导入的方式,实现路由懒加载
{
path: "/article/:id",
name: "articleDetail",
component: () => import("@/views/article_detail.vue"),
}
打包的时候就会根据路由去创建不同的路由文件,只有访问对应路由的时候,才会加载对应的js文件

路由元信息
在定义路由的时候,可以额外给路由设置一些信息
在使用路由信息的时候可以获取到这些元信息
{
path: "/admin",
name: "admin",
component: AdminView,
meta:{
title: "后台",
login: true
},
children: [
{
path: "", // /admin
name: "adminHome",
component: AdminHome,
meta:{
title: "后台Home"
},
},
{
path: "info", // /admin/info
name: "infoHome",
component: infoHome,
meta:{
title: "后台Info"
login: false
},
}
]
},
这个meta有一个特点,如果当前路由没有对应的值
那么就会往上找父级路由有没有对应的meta信息
比如 在adminHome这个页面,获取meta就是 {title: '后台Home', login: true}
在infoHome页面获取的meta就是 {title: '后台Info', login: false}
ts定义
import 'vue-router';
declare module 'vue-router' {
interface RouteMeta {
title?: string
}
}
作用:
- 在路由守卫的时候,通过元信息表示这个路由是否需要登录
- 在类似菜单这样的组件的时候,直接进行数据展示
路由守卫
路由守卫(Navigation Guards)是 Vue Router 提供的钩子函数,用于在路由跳转过程中进行拦截、校验或处理,比如登录验证、权限控制、页面跳转前的确认等。
路由守卫主要分为 3 类:全局守卫、路由独享守卫、组件内守卫。
全局守卫
全局守卫注册在路由实例上,对所有路由跳转生效,常用的有:
router.beforeEach:路由跳转前触发(最常用)router.afterEach:路由跳转后触发(无拦截能力,仅做后置处理)
// 全局前置守卫:登录验证示例
router.beforeEach((to, from, next) => {
// to:目标路由对象
// from:当前即将离开的路由对象
// next:继续跳转的函数(必须调用,否则路由不会跳转)
// 判断目标路由是否需要登录
if (to.meta.requiresAuth) {
// 模拟从本地存储获取登录状态
const isLogin = localStorage.getItem('token')
if (isLogin) {
next() // 已登录,正常跳转
} else {
next('/login') // 未登录,跳转到登录页
}
} else {
next() // 不需要登录,直接跳转
}
})
// 全局后置守卫:页面跳转后处理(比如修改页面标题)
router.afterEach((to, from) => {
if (to.meta.title) {
document.title = to.meta.title // 从路由元信息获取标题
}
})
路由独享守卫
直接在路由配置中定义,仅对当前路由生效,仅 beforeEnter:
const routes = [
{
path: '/detail/:id',
name: 'Detail',
component: () => import('@/views/Detail.vue'),
// 路由独享守卫
beforeEnter: (to, from, next) => {
// 校验路由参数是否合法
if (Number.isNaN(Number(to.params.id))) {
next('/404') // 参数不合法,跳转到404
} else {
next() // 参数合法,正常跳转
}
},
meta: { title: '详情页' }
}
]
组件内守卫
在 Vue3 组件中,会触发两个路由钩子函数,如下:
onBeforeRouteUpdate:路由参数更新时触发(比如从/detail/1跳转到/detail/2)onBeforeRouteLeave:离开组件前触发(比如确认是否放弃未保存的表单)
<template>
<div>个人中心</div>
</template>
<script setup>
import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
// 路由参数更新
onBeforeRouteUpdate((to, from, next) => {
console.log('参数更新', to.params)
next()
})
// 离开组件前
onBeforeRouteLeave((to, from, next) => {
const confirmLeave = confirm('是否确定离开?')
next(confirmLeave) // 直接传布尔值,true=跳转,false=取消
})
</script>
正则表达式路由
定义路由的时候,除了使用:id表示动态路径
还可以使用:params(正则表达式) 去编写正则表达式的路由规则,更加灵活
{
path: '/user/:id(\\d+)', // 注意:JS中反斜杠需要转义,所以写 \\d+ 而不是 \d+
name: 'User',
component: () => import('../views/User.vue')
},
// 匹配 /article/YYYY-MM-XXXX 格式(年份4位、月份2位、ID任意数字)
{
path: '/article/:dateId(\\d{4}-\\d{2}-\\d+)',
name: 'Article',
component: () => import('../views/Article.vue')
},
404路由
用于匹配所有未定义的路由并展示对应的页面
{
// 通配符匹配所有未定义的路径
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}
兄弟组件通过路由通信
之前我们讲组件通信的时候,讲到过可以使用路由进行组件通信,实现起来也很简单
A组件通过修改路由参数,B组件监听路由参数即可完成
通过路由通信有一个好处就是刷新之后路由参数还在
a组件
<script setup lang="ts">
import {onBeforeRouteUpdate} from "vue-router";
import {ref, watch} from "vue";
import {useRoute} from "vue-router";
const route = useRoute()
const tag = ref("")
// 刷新的时候不会调这个回调
// onBeforeRouteUpdate((to, from, next)=>{
// const t = to.query.tag
// if (t){
// tag.value = t
// }
// next()
// })
watch(()=>route.query.tag, (newValue, oldValue)=>{
tag.value = newValue
}, {immediate: true})
</script>
<template>
<div>
文章列表 搜索的内容是: {{ tag }}
</div>
</template>
<style scoped>
</style>
b组件
<script setup lang="ts">
import {useRouter} from "vue-router";
import {useRoute} from "vue-router";
const route = useRoute()
const router = useRouter()
function search(tag: string) {
router.push({
name: route.name, query: {
tag: tag
}
})
}
</script>
<template>
<div>
标签云
<div>
<span @click="search('python')">python</span>
<span @click="search('go')">go</span>
<span @click="search('JavaScript')">JavaScript</span>
</div>
</div>
</template>
<style scoped>
</style>
动态路由
router.addRoute() 是 Vue Router 实例的方法,用于在应用运行时动态注册新的路由规则,而不是在初始化时一次性定义所有路由。它弥补了静态路由配置的不足,特别适合需要根据用户权限、异步数据等动态生成路由的场景。
基础语法
// 语法1:添加单个路由
router.addRoute(routeRecord)
// 语法2:添加到指定的路由分组(嵌套路由)
router.addRoute(parentName, routeRecord)
在前端加载
// 动态添加路由 /route
router.addRoute({
path: "/route", // /admin/info
name: "route",
component: () => import("@/views/route.vue"),
})
// 添加到admin1这个路由组里面 // /admin1/route
router.addRoute("admin1", {
path: "route", // /admin/info
name: "route",
component: () => import("@/views/route.vue"),
})
异步加载
请求路由文件,然后加载到路由里面
let addRouteStatus = false
const viewModules = import.meta.glob('@/views/**/*.vue')
// 路由前置守卫
router.beforeEach(async (to, from, next) => {
if (!addRouteStatus) {
const routeList = await getRoute()
const route = routeList[0]
const componentPath = `/src/${route.component}`
const component = viewModules[componentPath]
router.addRoute({
name: route.name,
path: route.path,
component: component,
})
router.addRoute({
path: "/:pathMatch(.*)*", // /admin/info
name: "notfound",
component: () => import("@/views/404.vue"),
})
addRouteStatus = true
return next({...to, replace: true})
}
console.log("前置 from", from)
console.log("前置 to", to)
if (to.meta.login) {
// 这个路由需要登录
const token = localStorage.getItem("token")
if (!token) {
Message.warning("需要登录")
next("/login")
return
}
}
next()
})
这里面有非常多的知识点
- 刷新404问题
- import.meta.glob