路由是 Vue3 开发中的重要部分,它允许页面的部分改变,本文从简单静态路由、路由工作模式、动态路由、多级路由、编程式路由等全面介绍了 Vue3 的路由方式
常见的需求是,一个网页由 导航栏 + 内容页 组成:
对于这种结构的网站来说,切换导航栏意味着内容页的变化,而不改变其他部分,在 Vue3 中,通过路由实现上面的需求:
简单静态路由
第一步,创建组件
路由控制的展示页面,其实质是一个个组件,与一般的组件并无任何不同。
Home.vue
<script setup lang="ts">
</script>
<template>
<div class="home">
<h2>这是首页</h2>
</div>
</template>
<style scoped>
.home {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
</style>
News.vue
<script setup lang="ts">
</script>
<template>
<div class="news">
<ul>
<li><a href="#">新闻</a></li>
<li><a href="#">新闻</a></li>
<li><a href="#">新闻</a></li>
<li><a href="#">新闻</a></li>
<li><a href="#">新闻</a></li>
</ul>
</div>
</template>
<style scoped>
.news {
padding: 0 20px;
display: flex;
justify-content: space-between;
height: 100%;
}
</style>
About.vue
<script setup lang="ts">
</script>
<template>
<div class="about">
<h2>这是关于页面</h2>
</div>
</template>
<style scoped>
.about {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
color: gray;
font-size: 18px;
}
</style>
注意:
- 一般组件:通过标签
<Demo />
引入组件- 路由组件:靠路由规则渲染出来,不显式存在组件标签
<Demo />
,可能存在to="/demo"
- 路由组件虽然与普通组件本质上没什么不同,但工程上通常把路由组件放在
pages
或者views
文件夹以区分一般组件的components
。
第二步,配置路由的规则并使用
Vue3 允许我们使用 createRouter
创建路由器,并配置路由规则、工作模式等路由器的行为模式。在配置路由后,别忘了在使用路由的应用中配置使用路由器。
/* 创建一个路由器,并暴露出去 */
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;
在 main.ts
中,通过下面的代码引入前面设置的路由到 app 上:
import {createApp} from "vue";
import App from "@/App.vue";
import router from '@/router';
// 创建一个 Vue 应用实例
const app = createApp(App);
// 使用路由
app.use(router);
// 挂载到根元素上
app.mount("#app")
第三步,配置路由的页面位置和跳转按钮
通过 RouterView
可以配置路由组件需要展示的位置,通过 RouterLink
可以配置跳转按钮,并通过 to
配置目标路由,active-class
配置生效时的样式。
<script setup lang="ts">
</script>
<template>
<div class="app">
<!-- 网站标题 -->
<div class="title">Vue3 路由</div>
<!-- 导航区 -->
<div class="navi">
<RouterLink to="/home" active-class="active">首页</RouterLink>
<RouterLink to="/news" active-class="active">新闻</RouterLink>
<RouterLink :to="{path: '/about'}" active-class="active">关于我们</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<!-- 对于路由组件,使用RouterView组件来展示 -->
<RouterView/>
</div>
</div>
</template>
<style scoped>
.app {
width: 100%;
height: 100%;
background-color: white;
}
.title {
text-align: center;
word-spacing: 5px;
margin: 30px auto 0;
height: 70px;
line-height: 70px;
background-image: linear-gradient(45deg, gray, white);
border-radius: 10px;
box-shadow: 0 0 2px;
font-size: 30px;
width: 90%;
}
.navi {
display: flex;
justify-content: space-around;
margin: 30px auto 0;
width: 90%;
}
.navi a {
display: block;
text-align: center;
width: 100%;
height: 40px;
line-height: 40px;
border: white solid;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.navi a.active {
background-color: #64967e;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
}
.main-content {
margin: 30px auto 0;
border-radius: 10px;
width: 90%;
height: 200px;
border: 1px solid;
}
</style>
注意:通过点击导航,视觉上「消失」了的路由组件,默认是被销毁掉的,需要的时候会重新挂载。
路由器的工作模式
路由器的工作模式一共有两种,分别是:
history
模式
更常用,to C
const router = createRouter({
history: createWebHistory(), // history模式
...
路径:http://localhost:5173/about
- 优点:URL 更美观,不带
#
,更接近传统网站 URL。 - 缺点:后期项目上线,需要服务端配合处理路径问题(配置 nginx 的 try_files),否则刷新会出现 404 报错。
hash
模式
后台管理常用,不需要美观,无需 SEO 优化
const router = createRouter({
history: createWebHashHistory(), // hash模式
...
路径:http://localhost:5173/#/about
- 优点:兼容性更好,不需要服务端处理路径。
- 缺点:URL 带有
#
不太美观,在 SEO 方面优化相对较差。
路由的历史记录模式
路由的历史记录模式配置的是进行路由的切换后,在浏览器中以何种形式展现这个记录。简单来说,就是控制用户能否通过浏览器的回退键回到上一页的行为。
push
模式
默认的模式,无需任何配置。路由的切换会被 push 到历史记录栈中去,通过回退键能够返回到上一页。
replace
模式
通过在 RouterLink
中配置 replace
属性实现,路由的切换会直接覆盖历史记录栈的顶部,也就是说,通过回退键不能返回上一页。
<RouterLink replace :to="{name:'home'}" active-class="active">首页</RouterLink>
更复杂地路由
:to
动态路由
前面介绍了通过设定 to
的地址确定目标,这是一种静态的路由方式,实际上,Vue3 允许我们向 to
传递一个对象,例如:
<RouterLink :to="{path: '/about'}" active-class="active">关于我们</RouterLink>
通过这种方式,可以更加灵活地实现路由的跳转。
name
命名路由
在配置路由时,除了设定路由的地址 path
,其实,还可以设置路由的名称,例如:
const router = createRouter({
history: createWebHistory(),
routes: [
{
name: 'anyName', // 配置路由的名称
path: '/home',
component: Home
},
...
配置了名称的路由,可以通过传递带 name
的对象给 to
实现根据名称跳转——当然本质上还是通过 path
跳转:
<RouterLink :to="{name:'anyName'}" active-class="active">首页</RouterLink>
命名路由的意义在于,当 path
很长时,提供了一种更加简洁的编写方式,换言之,命名路由实质上是一种语法糖。
redirect
路由重定向
可以将一个路由地址重定向到另一个路由地址,通过配置 redirect
实现。
// 创建路由器
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes: [ // 路由规则
{
name: 'home',
path: '/home',
component: Home
},
{
path: '/',
redirect: '/home' // 重定向到首页
},
...
]
});
多级路由
有时,我们在某一个路由组件中,需要再做路由,例如:
在 Vue3 中, 可以通过 children
定义路由的子路由,从而实现多级路由。
多级路由示例
例如我们可以修改 简单静态路由 一节的代码,实现在新闻组件下继续点击每一条具体新闻时,在右侧显示新闻的具体内容:
此时,我们可以修改路由配置的代码,新增一个子路由:
// 创建路由器
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes: [ // 路由规则
...
{
name: 'news',
path: '/news',
component: News,
children: [ // 子路由
{
name: "detail",
path: 'detail', // 此处不要加'/',才能通过 '/news/detail' 访问此组件
component: NewsDetail
}
]
},
...
对于多级路由的访问,与静态是完全一致的,例如在 News.vue
中:
<template>
<div class="news">
<!-- 导航区 -->
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink to="/news/detail">{{ news.title }}</RouterLink>
</li>
</ul>
<!-- 展示区 -->
<div class="news-content">
<RouterView/>
</div>
</div>
</template>
query 传参
query 是通过 HTTP 请求的 query 参数传递给路由组件,在路由组件中可以使用 useRoute.query
获取对应的参数。通过这种方式传参,会在请求的参数列表中,带上查询的参数,例如:
http://localhost:5173/news/detail?id=adiojo02&title=好消息!&content=快过年了
发送方式
第一种发送方式,通过 js 的字符串模板拼接:
<!-- 导航区 -->
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">
{{ news.title }}
</RouterLink>
</li>
</ul>
第二种发送方式,通过传递对象:
<!-- 导航区 -->
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink :to="{
name: 'detail',
// 或者基于 path
// path: '/news/detail',
query: {
id: news.id,
title: news.title,
content: news.content
}
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
接收方式
通过 useRoute().query
获取。
NewsDetail.vue:
<script setup lang="ts">
import {useRoute} from "vue-router";
const route = useRoute();
</script>
<template>
<ul class="news-list">
<li>编号:{{ route.query.id }}</li>
<li>标题:{{ route.query.title }}</li>
<li>内容:{{ route.query.content }}</li>
</ul>
</template>
<style scoped>
.news-list {
list-style: none;
padding-left: 20px;
}
.news-list > li {
line-height: 30px;
}
</style>
params 传参
params
传参,相较于 query
传参更加简洁。它是 RestFul 风格的传参方式,通过这种方式传参,形如:
http://localhost:5173/news/detail/adiojo02/好消息!/快过年了
配置路由
params
传参不是 query
那样的 key-value
式传参,因此需要事先规定好每个位置的含义,配置好 params 传参的接收方式,并与路由做区分。
支持通过在参数末尾加
?
来控制参数的必要性,默认所有参数都必需。
// 创建路由器
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes: [ // 路由规则
...
{
name: 'news',
path: '/news',
component: News,
children: [
{
name: "detail",
path: 'detail/:id/:title/:content?', // 占位,配置 params 传参的接收方式
component: NewsDetail
}
]
},
...
发送方式
第一种发送方式,通过 js 字符串拼接:
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">
{{ news.title }}
</RouterLink>
</li>
</ul>
第二种发送方式,通过传递对象:
<!-- 导航区 -->
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink :to="{
// 不支持基于 path
name: 'detail',
params: {
id: news.id,
title: news.title,
content: news.content
}
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
注意:
- 对于
params
传参不支持path
路由。- 参数不支持传送数组、对象类型。
接收方式
完全类似 query
传参,通过 useRoute().param
获取 params
参数。
<script setup lang="ts">
import {useRoute} from "vue-router";
const route = useRoute();
</script>
<template>
<ul class="news-list">
<li>编号:{{ route.params.id }}</li>
<li>标题:{{ route.params.title }}</li>
<li>内容:{{ route.params.content }}</li>
</ul>
</template>
<style scoped>
.news-list {
list-style: none;
padding-left: 20px;
}
.news-list > li {
line-height: 30px;
}
</style>
用路由规则的 props 简化传参
前面我们介绍了使用 query
和使用 params
传参的两种方式,但不管是哪种参数,使用的时候都会出现形如 route.params.id
形式,这不够简洁,有没有方式能简化参数的使用呢?
用 js 的解构(不推荐)
当然,我们可以使用 js 的解构简化参数的使用:
let {params} = toRefs(route); // 这里的 toRefs 是必须的,作用是保留 params 的相应式
但是,使用时,还是需要:
<ul class="news-list">
<li>编号:{{ params.id }}</li>
<li>标题:{{ params.title }}</li>
<li>内容:{{ params.content }}</li>
</ul>
诚然,我们可以再解构一遍,但这太过麻烦。
通过路由规则的 props
简化传参
Vue3 提供了更加简易的传参方式,这是通过路由规则配置实现的,其支持三种写法:
-
简单写法
// 创建路由器 const router = createRouter({ history: createWebHistory(), // 路由器的工作模式 routes: [ // 路由规则 ... { name: 'news', path: '/news', component: News, children: [ { name: "detail", path: 'detail/:id/:title/:content', component: NewsDetail, // 第一种写法,将路由收到的所有 params 参数都传递给 NewsDetail 组件 // 不支持 query 参数 props: true } ] }, ...
-
函数式写法(推荐)
// 创建路由器 const router = createRouter({ history: createWebHistory(), // 路由器的工作模式 routes: [ // 路由规则 ... { name: 'news', path: '/news', component: News, children: [ { name: "detail", path: 'detail/:id/:title/:content', component: NewsDetail, // 第二种写法,可以自己决定将哪些参数传递给 NewsDetail 组件 // 支持 query 参数 props: (route) => { return route.params; // 或者 route.query 都可以,这里的 route 就是前面的 useRoute() } } ] }, ...
-
对象写法(不常用)
// 创建路由器 const router = createRouter({ history: createWebHistory(), // 路由器的工作模式 routes: [ // 路由规则 ... { name: 'news', path: '/news', component: News, children: [ { name: "detail", path: 'detail/:id/:title/:content', component: NewsDetail, // 第三种写法,对象形式(用得少) props: { id: '123', title: 'title', content: 'content' } } ] }, ...
配置完 props: true
后,Vue3 会自动将 path
配置的参数以 props
的形式传递给组件,此时,在组件中只需通过 defineProps
接收父组件传递的参数即可:
<script setup lang="ts">
defineProps(
['id', 'title', 'content']
);
</script>
<template>
<ul class="news-list">
<li>编号:{{ id }}</li>
<li>标题:{{ title }}</li>
<li>内容:{{ content }}</li>
</ul>
</template>
由于传参和
props
是等价的,限制接收类型和默认值(见前面讲 props 的章节)等方式都是有效的。
编程式导航(重要)
细心的你可能发现,RouterLink
最终会被翻译成 a
标签,所以前面针对 a
标签的样式会对 RouterLink
生效。可是,有时我们希望通过其他组件、按钮、事件触发路由组件的变化,这时我们就需要用到编程式导航。
实际上,我们可以通过 useRouter
获取路由本身,使用其中的 replace(to)
、push(to)
等函数操作路由跳转。其中 to
的内容和 RouterLink
的类似,可以用字符串,可以用对象,可以传参。
注意区分
useRouter
和useRoute
。
例如:进入首页 3s 后,跳转到关于页面。
<script setup lang="ts">
import {onMounted} from "vue";
import {useRouter} from "vue-router";
// 获取路由本身
const router = useRouter();
onMounted(
() => {
setTimeout(() => {
console.log('3s 到了')
// 让路由实现跳转
router.push({name: 'about'}) // push 模式跳转
// router.replace('/about') // replace 模式跳转
}, 3000);
}
);
</script>
<template>
<div class="home">
<h2>这是首页</h2>
</div>
</template>
<style scoped>
.home {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
</style>