8. Vue3 路由
8.1 对路由的理解
-
路由定义:路由是根据不同的地址(URL)展示不同内容的机制。通过对 URL 的识别与匹配,将用户引导至对应的内容页面
-
路由本质:就是一组key(通常是指路由的路径(如
/home
、/news
等))-value(是与该路径对应的组件(如Home.vue
、News.vue
等组件))的对应关系 -
前端使用路由的目的:前端使用路由是为了实现单页面应用(SPA应用),单页面应用能够在不重新加载整个页面的情况下,动态更新页面内容
-
路由器的作用:当存在多个路由时,就需要路由器进行统一管理。它负责监控 URL 的变化,根据预先设定的规则,找到对应的路由配置,并加载相应组件
-
route规则:route 规则的核心是根据路径寻找与之对应的组件。通过对 URL 路径的解析和匹配,路由器能够确定需要加载的组件,从而展示正确的页面内容
-
流程:点击——>路径变化——>路由器捕获——>寻找相应的route规则——>把相应的组件挂载到对应的地方(当用户进行点击操作,触发路径变化,路由器会捕获到这一变化,依据 route 规则,在路由配置中寻找匹配的路径,然后将对应的组件挂载到指定位置,完成页面内容的更新)
8.2 基本切换效果
8.2.1 前置条件:安装 Vue Router
场景:使用路由功能前需全局安装 Vue3 对应的路由库
npm i vue-router
8.2.2 路由配置核心流程(以 router/index.ts 为例)
整体代码:
import { createRouter, createWebHistory} from 'vue-router';
import Home from '@/pages/Home.vue';
import News from '@/pages/News.vue';
import About from '@/pages/About.vue';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/home',
component: Home
},
{
path: '/news',
component: News
},
{
path: '/about',
component: About
}
]
});
export default router;
目标:创建路由器并配置路由规则,让不同 URL 显示对应组件
- 引入核心工具:
-
createRouter
:创建路由器实例的函数 -
createWebHistory
:设置路由模式为 HTML5 历史模式(无 # 号)
import { createRouter, createWebHistory } from 'vue-router'
- 引入页面组件:从项目路径中引入需要显示的组件(如首页、关于页)
import Home from '@/pages/Home.vue' // @ 通常代表 src 目录
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
- 创建路由器并配置规则:
routes
数组定义路由规则,每个对象包含:path
:路由路径(浏览器地址栏显示的 URL)component
:对应路径显示的组件
const router = createRouter({
history: createWebHistory(), // 使用历史模式
routes: [
{
path: '/home',// 访问 /home 显示 Home 组件
component: Home
},
{
path: '/news',// 访问 /news显示 News组件
component: News
},
{
path: '/about',// 访问 /about 显示 About 组件
component: About
}
]
})
- 暴露路由器:将配置好的路由器导出,供 main.ts 引用
export default router
8.2.3 在 main.ts 中启用路由
操作:将路由器挂载到 Vue 应用中,激活路由功能
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index' // 引入路由器
const app = createApp(App)
app.use(router) // 使用路由器
app.mount('#app') // 挂载应用到页面容器
8.2.4 在 App.vue 中实现导航与内容展示
整体代码:
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="increment">点我+1</button>
<button @click="decrement">点我-1</button>
<hr>
<img v-for="(u,index) in dogList.urlList" :key="index" :src="(u as string)">
<span v-show="dogList.isLoading">加载中......</span><br>
<button @click="getDog">再来一只狗</button>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({
name:'App',
})
</script>
<script setup lang="ts">
import useSum from './hooks/useSum'
import useDog from './hooks/useDog'
let {sum,increment,decrement} = useSum()
let {dogList,getDog} = useDog()
</script>
- 导航区:使用 RouterLink 组件
-
作用:生成路由链接,点击时切换页面(不会刷新浏览器)
-
关键属性:
to
:目标路由路径(如/home
)active-class
:选中时添加的 CSS 类名(用于高亮当前链接)
<template>
<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>
</template>
<script setup>
import { RouterLink } from 'vue-router' // 引入导航组件
</script>
- 展示区:使用 RouterView 组件
- 作用:作为路由组件的占位符,匹配到的组件会在此处渲染
<template>
<div class="main-content">
<RouterView></RouterView> <!-- 路由匹配的组件会显示在这里 -->
</div>
</template>
<script setup>
import { RouterView } from 'vue-router' // 引入占位符组件
</script>
8.3 两个注意点
-
组件存放位置
-
路由组件:也就是直接和路由配置绑定的页面级组件,一般存放在
pages
或者views
文件夹 -
一般组件:是那些被路由组件或者其他组件引用的小部件,通常放在
components
文件夹。像按钮、导航栏、表单输入框这些复用性比较高的组件都属于一般组件
-
-
路由组件的卸载与挂载机制:当我们通过点击导航切换页面时,原来显示的路由组件在视觉上 “消失” 了,这时候默认情况下它是被卸载了。也就是说,这个组件的实例会被销毁,相关的资源也会被释放。当我们再次导航到这个页面时,组件会重新挂载,也就是重新创建实例并渲染
8.4 路由器工作模式
总结:
-
如果更看重 URL 的美观性,不介意服务端配置,选择
history
模式 -
如果更关注兼容性,不想处理服务端路径问题,选择
hash
模式
8.4.1 history 模式
介绍:这种模式的 URL 更好看,没有 #符号,和传统网站的 URL 很像。但有个缺点,项目上线后需要服务端配合处理路径,不然刷新页面会出现 404 错误
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = createRouter({
history: createWebHistory(), // 使用history模式
routes
})
export default router
8.4.2 hash 模式
介绍:它的兼容性更好,因为不需要服务器端处理路径。不过 URL 带有 #符号,不太美观,而且在 SEO 优化方面效果相对较差
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/contact',
name: 'Contact',
component: Contact
}
]
const router = createRouter({
history: createWebHashHistory(), // 使用hash模式
routes
})
export default router
8.5 to的两种写法
在 Vue Router 中,<router-link>
组件用于实现路由导航,它的 to
属性有两种作用相同的写法
8.5.1 字符串写法
-
语法:直接将路由路径作为字符串传递给
to
属性 -
优点:简单直观
-
缺点:在跳转的时候没办法传递参数
-
例子:
<router-link to="/home">首页</router-link>
8.5.2 对象写法
-
语法:将一个对象传递给
to
属性,这个对象可以包含路径、参数、查询参数等信息 -
优点:灵活强大
-
例子:
<router-link :to="{ path: '/home', query: { id: 1 }, hash: '#section' }">首页</router-link>
代码解释:
path
代表目标路由的路径query
是一个对象,用于传递查询参数,最终会在 URL 中显示为?id=1
hash
表示 URL 中的锚点,会显示为#section
8.6 命名路由
介绍:命名路由就是给路由规则起个名字,方便后续使用
// router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home.vue'
import UserList from '../views/UserList.vue'
import UserDetail from '../views/UserDetail.vue'
const routes: Array<RouteRecordRaw> = [
{
name: 'home',
path: '/',
component: Home
},
{
name: 'userList',
path: '/users',
component: UserList
},
{
name: 'userDetail',
path: '/users/:id',
component: UserDetail,
props: true // 开启props传参
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
<!-- Home.vue -->
<template>
<div class="container">
<h1>用户管理系统</h1>
<!-- 字符串写法(不推荐) -->
<router-link to="/users" class="btn">用户列表</router-link>
<!-- 对象写法:名字跳转 -->
<router-link :to="{ name: 'userList' }" class="btn">用户列表</router-link>
<!-- 对象写法:路径跳转 -->
<router-link :to="{ path: '/users' }" class="btn">用户列表</router-link>
<!-- 带参数的跳转 -->
<router-link :to="{ name: 'userDetail', params: { id: 1 } }" class="btn">
用户详情
</router-link>
<!-- 在TS中使用编程式导航 -->
<button @click="goToUserDetail(2)" class="btn">查看用户2</button>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
const goToUserDetail = (id: number) => {
router.push({ name: 'userDetail', params: { id } })
}
</script>
8.7 嵌套路由
介绍:允许你在一个路由组件内部再嵌套其他路由组件
步骤:
- 编写
News
的子路由:Detail.vue
<template>
<ul class="news-list">
<li>编号:xxx</li>
<li>标题:xxx</li>
<li>内容:xxx</li>
</ul>
</template>
<script setup lang="ts" name="About">
.....
</script>
......
- 配置路由规则,使用
children
配置项
const router = createRouter({
history:createWebHistory(),
routes:[
{
name:'zhuye',
path:'/home',
component:Home
},
{
name:'xinwen',
path:'/news',
component:News,
children:[
{
name:'xiang',
path:'detail',
component:Detail
}
]
},
{
name:'guanyu',
path:'/about',
component:About
}
]
})
export default router
- 跳转路由(记得要加完整路径):
<router-link to="/news/detail">xxxx</router-link>
<!-- 或 -->
<router-link :to="{path:'/news/detail'}">xxxx</router-link>
- 在外层的
News
组件中用<router-view>
占位
<template>
<div class="news">
<nav class="news-list">
<RouterLink v-for="news in newsList" :key="news.id" :to="{path:'/news/detail'}">
{{news.name}}
</RouterLink>
</nav>
<div class="news-detail">
<RouterView/>
</div>
</div>
</template>
8.7.1 query参数
-
介绍:query 参数是路由传参的一种方式,类似 URL 后面的查询字符串(?key=value)
-
两种写法:
- 字符串写法:直接在路径后面拼接参数
<!-- 基础示例 -->
<router-link to="/news/detail?a=1&b=2&content=欢迎你">跳转</router-link>//传的是固定的值
<!-- 动态拼接(使用插值表达式) -->
<router-link :to="`/news/detail?id=${news.id}&title=${news.title}`">
{{news.title}}
</router-link>
- 对象写法:通过
to
的对象形式传递
<!-- 使用 path 跳转 -->
<RouterLink
:to="{
path: '/news/detail',
query: {
id: news.id, // 假设 news 是组件内的数据
title: news.title,
content: news.content
}
}"=
{{news.title}}
</RouterLink>
<!-- 使用 name 跳转(效果相同) -->
<RouterLink
:to="{
name: 'NewsDetail', // 路由配置中的 name
query: {
id: news.id,
title: news.title
}
}"
{{news.title}}
</RouterLink>
- 接收 query 参数:在目标组件中使用
useRoute
钩子获取参数
<template>
<div>
<h1>{{ route.query.title || '默认标题' }}</h1>
<p>ID: {{ route.query.id }}</p>
<div>{{ route.query.content }}</div>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()
// 打印参数(开发时调试用)
console.log(route.query) // 输出: { id: '1', title: '文章标题', content: '内容...' }
// TypeScript 类型断言(可选)
const newsId = route.query.id as string
const newsTitle = route.query.title as string
</script>
8.7.2 params参数
-
两种写法:
- 字符串写法:直接在路径中拼接参数
<RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">
{{ news.title }}
</RouterLink>
- 对象写法:使用 name 和 params 对象传参
<RouterLink
:to="{
name: 'NewsDetail', // 必须用name,不能用path
params: {
id: news.id,
title: news.title,
content: news.content
}
}"
>
{{ news.title }}
</RouterLink>
- 路由配置中占位:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history:createWebHistory(),
routes:[
{
name:'zhuye',
path:'/home',
component:Home
},
{
name:'xinwen',
path:'/news',
component:News,
children:[
{
name:' NewsDeta',
path:'detail/:id/:title/:content?',// 占位参数用:开头
component:Detail
}
]
},
{
name:'guanyu',
path:'/about',
component:About
}
]
})
export default router
- 在相应组件中接收参数:
<template>
<div>
<h1>{{ route.params.title }}</h1>
<p>{{ route.params.content }}</p>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
// 声明route类型
const route = useRoute<{
params: {
id: string
title: string
content: string
}
}>()
// 打印参数验证
console.log('接收的参数:', route.params)
</script>
8.8 路由的props配置
介绍:让组件更便捷地接收路由参数
8.8.1 对象写法
把固定对象里的键值对当作 props 传递给组件
{
path: '/user',
name: 'User',
component: UserComponent,
props: { role: 'admin', theme: 'dark' }
}
在 UserComponent 组件里,我们可以这样接收 props:
<template>
<div>当前角色:{{ role }}</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
role: string;
theme: string;
}>()
</script>
注意:对象写法传递的是静态数据,不会随着路由的变化而改变
8.8.2 布尔值写法
当 props 设为 true 时,路由会自动把 params 参数转化为 props 传递给组件
{
path: '/post/:id',
name: 'Post',
component: PostComponent,
props: true
}
在 PostComponent 组件中接收 props 的方式如下:
<template>
<div>文章ID:{{ id }}</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
id: string;
}>()
</script>
注意:这种写法只对 params 参数有效,query 参数是无法传递的
8.8.3 函数写法
允许我们对参数进行处理之后再传递给组件
{
path: '/search',
name: 'Search',
component: SearchComponent,
props: (route) => ({
keyword: route.query.q as string,
page: parseInt(route.query.page as string || '1')
})
}
在 SearchComponent 组件中接收 props:
<template>
<div>搜索关键词:{{ keyword }}, 页码:{{ page }}</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
keyword: string;
page: number;
}>()
</script>
8.8.4 总结
8.8.4.1场景一
只传 params 参数(动态路由)
// 路由配置
{
path: '/article/:id',
props: true // 布尔值写法
}
// 组件直接用props
const props = defineProps<{ id: string }>()
8.8.4.2场景二
只传固定数据(与路由参数无关)
// 路由配置
{
path: '/about',
props: { version: '1.0.0' } // 对象写法
}
// 组件直接用props
const props = defineProps<{ version: string }>()
8.8.4.3场景三
传 query 参数 或 需要处理参数
// 路由配置
{
path: '/search',
props: (route) => ({
keyword: route.query.q as string, // 函数写法
page: Number(route.query.page || 1) // 参数转换
})
}
// 组件直接用props
const props = defineProps<{ keyword: string; page: number }>()
8.9 replace属性
-
介绍:RouterLink 组件的
replace
属性用于控制路由跳转时浏览器历史记录的处理方式 -
简单来说:决定了点击链接后,浏览器的历史记录是新增一条记录,还是替换当前记录
-
历史记录的两种模式:
-
push(默认):追加一条新的历史记录。点击浏览器的 “后退” 按钮时,会返回到上一个页面
-
replace:替换当前的历史记录。点击 “后退” 按钮时,会直接跳过当前页面
-
-
使用 replace 属性:
<!-- 使用replace属性 -->
<RouterLink to="/news" replace>新闻</RouterLink>
-
使用场景:
-
表单提交后跳转:避免用户刷新页面导致重复提交
-
登录 / 注册后跳转:登录成功后替换登录页面,防止后退回到登录页
-
重定向:当用户访问需要权限的页面时,重定向到登录页并替换当前历史
-
8.10 编程式路由导航
-
开始,从
vue-router
中导入两个 hook 函数import { useRoute, useRouter } from ‘vue-router’
useRoute()
:获取当前路由的信息
useRouter()
:获取路由实例,用于导航操作
- 完整的组件示例:
<template>
<div>
<h1>用户详情页</h1>
<p>用户ID: {{ route.params.id }}</p>
<p>查询参数: {{ route.query.keyword }}</p>
<button @click="goBack">返回</button>
<button @click="goToUserList">用户列表</button>
<button @click="goToUserProfile">个人资料</button>
</div>
</template>
<script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router'
// 获取当前路由信息
const route = useRoute()
// 获取路由实例
const router = useRouter()
// 返回上一页
const goBack = () => {
router.back()
}
// 跳转到用户列表页,使用路径方式
const goToUserList = () => {
router.push('/users')
// 或者使用命名路由
// router.push({ name: 'UserList' })
}
// 跳转到个人资料页,携带参数
const goToUserProfile = () => {
// 携带查询参数,URL会变成 /profile?name=John&age=30
router.push({
path: '/profile',
query: {
name: 'John',
age: 30
}
})
// 或者携带动态路由参数(需要路由配置中定义:id)
// router.push({
// name: 'UserProfile',
// params: {
// id: '123'
// }
// })
}
// 打印当前路由信息
console.log('当前路由参数:', route.params)
console.log('当前查询参数:', route.query)
</script>
8.11 重定向
-
介绍:当用户访问一个路径时,自动跳转到另一个已有的路由
-
例子:首先需要安装并配置全局vue-router,然后在路由配置中添加重定向规则
// router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
// 定义路由
const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/about' // 根路径重定向到/about
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue') // 假设我们有一个About组件
},
{
path: '/home',
name: 'Home',
component: () => import('../views/Home.vue') // 假设我们有一个Home组件
},
{
path: '/old-home',
redirect: '/home' // 旧的home路径重定向到新的home路径
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
8.11.1 路径重定向
直接指定要跳转的路径
{
path: '/old-path',
redirect: '/new-path'
}
8.11.2 命名路由重定向
通过路由名称跳转
{
path: '/',
redirect: { name: 'Home' }
}
8.11.3 动态重定向
根据条件动态决定跳转路径
{
path: '/user/:id',
redirect: to => {
// to是目标路由对象,可以获取路径参数等信息
return { name: 'User', params: { id: to.params.id } }
}
}