这个东西学过Vue2的懂得都懂,是在单页应用 (SPA) 中将浏览器的 URL 和用户看到的内容绑定起来。当用户在应用中浏览不同页面时,URL 会随之更新,但页面不需要从服务器重新加载,也就是浏览器不会刷新。我们以前用a
标签跳转页面查看不同的内容时页面会刷新一下,用户体验不是很好,而路由可以帮助我们通过不同的url
在同一个页面不用刷新就可以展示出不同的内容,用户体验极好。
路由的基本使用
前提准备
我们先来搭一个基本的使用场景。
<template>
<div class="screen-view">
<!-- 导航区 -->
<div class="header">
<a href="">路由1</a>
<a href="">路由2</a>
<a href="">路由3</a>
</div>
<!-- 展示区 -->
<div class="container"></div>
</div>
</template>
到时候我们点击哪个路由,对应的页面就会在下面的方框里面展示,现在我们要先去安装一下路由器。
安装vue-router
要想使用路由器,我们要先安装vue-router
,当然也可以在创建Vue3项目的时候直接选择配置上这个vue-router
。
npm i vue-router
配置路由器
安装好以后我们要在src
文件夹下创建一个新的文件夹用来配置路由器,这个文件夹一般命名为router
,然后里面创建一个index.ts
文件用来创建并配置路由器。
然后我们就在这个文件里面进行一些配置,首先肯定是引入创建路由器的函数,Vue3跟Vue2不一样的地方就是Vue3把每一个功能模块都变成了单独的函数,比如创建响应式数据就是ref
或者reactive
函数,创建监视器就是watch
函数,这里创建路由器也是如此,从vue-router
引入createRouter
函数。
import { createRouter } from 'vue-router'
我们就通过createRouter
函数去创建一个路由器,createRouter
函数里面接收一个参数对象,去配置我们这个路由器,其中有一个属性叫做routes
,这个属性是一个数组,里面存放的就是不同的路由信息。routes
数组的每一项都是一个对象,存放的是每一个路由的信息,其中有最重要的有三个属性,path
是路由的访问路径,name
是路由的名称,component
是该路由显示的组件内容。
注意一下,router
是路由器,上面存放着整个路由器的信息和路由器的相关操作。route
是路由,存放着某个路由的所有信息。
const router = createRouter({
routes: [
{
path: '/route1',
name: "routeName1",
component: () => import("@/views/route1/index.vue"),
}
]
})
其实这个时候是会报错的,因为配置路由器的时候还少了一项,就是路由器的工作模式,路由器有两种工作模式,hash
模式和history
模式。这里我们用history
模式,我们要从vue-router
引入一个函数createWebHistory
。
import { createRouter, createWebHistory } from 'vue-router'
然后在配置路由器的时候添加一个属性history
,然后把createWebHistory
函数赋值给它,这么一来路由器的工作模式就配置好了,此时这个代码就不会报错了。完整的代码如下:
// 创建一个路由器并且暴露出去
// 第一步:引入 vue-router
import { createRouter, createWebHistory } from 'vue-router'
// 第二步:创建一个路由器
const router = createRouter({
// 第三步:配置路由器
history: createWebHistory(), // 路由器的工作模式
routes: [ // 一个一个的路由信息
{
path: '/route1',
name: "routeName1",
component: () => import("@/views/route1/index.vue"),
}
]
})
// 将路由器暴露出去
export default router
将路由器使用到Vue上
我们创建配置完路由器以后得用上啊,不然白配置了,我们需要在创建Vue应用的时候,让这个路由器使用到Vue应用上,所以我们要在main.ts
里面引入这个路由器,并且通过use
方法让Vue使用这个路由器。
import { createApp } from 'vue' // createApp用来创建VUE应用的
import App from './App.vue' // 这是VUE应用的根组件
import router from './router' // 引入路由器
const app = createApp(App) // 创建一个名字叫app的VUE应用且根组件为App
app.use(router) // VUE应用使用路由器
app.mount('#app') // 将VUE应用挂在到一个id为app的容器里,这个容器就在前面说的入口文件index.html里
配置RouterView
这个时候我们先把代码补充完,原本三个路由按钮,所以我们也要创建三个路由信息出来,并创建好对应的组件。
对应组件的内容为我是路由1,我是路由2,我是路由3。
route1/index.vue
<template>
<div>我是路由1</div>
</template>
<script lang="ts" setup></script>
那现在问题来了,路由器创建好了,路由信息和路由对应的组件也都创建好了,那是不是就可以使用了呢?
我们来试一试,我们给页面的url
后面加上路由的路径。
会发现展示区内并没有我们想象的展示出对应的内容,是不是还差点什么呢?其实在我们在给页面的url
后面加上/route
的时候路由器已经监视到url的变化,它就会去找routes
里的路由信息,看看是否有这个路径,然后发现路由信息里有这个路径,也找到了这个路径对应的组件内容component
,接下来就该想这个组件内容应该放在哪,可是这个时候因为我们没有配置,所以此时路由器也懵了,它也不知道这个组件内容应该展示在哪里。
我们看前面的代码:
<template>
<div class="screen-view">
<!-- 导航区 -->
<div class="header">
<a href="route1">路由1</a>
<a href="route2">路由2</a>
<a href="route3">路由3</a>
</div>
<!-- 展示区 -->
<div class="container">
</div>
</div>
</template>
虽然我们注释了展示区,但是这只是给我们提示的,它并没有实际作用,页面此时不知道路由对应的组件内容应该展示在哪,所以我们需要从vue-router
上引入一个新的东西RouterView
路由器视图,然后把它放在我们想要展示内容的地方。
<template>
<div class="screen-view">
<!-- 导航区 -->
<div class="header">
<a href="route1">路由1</a>
<a href="route2">路由2</a>
<a href="route3">路由3</a>
</div>
<!-- 展示区 -->
<div class="container">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup>
import { RouterView } from "vue-router";
</script>
这个时候路由器就知道把内容展示在哪里了,我们再试一下:
此时就可以实现路由的基本使用了。
但是我们不能每次切换内容,都要去手动修改url
吧,这时候我们再从vue-router
上引入一个RouterLink
,用它来替换我们前面前面代码上的a
链接,再把href
属性换成to
属性,to
属性的值就是我们前面配置路由信息的时候写的路由路径,此时就可以实现点击链接进行切换内容了。
我们也可以去实现点击的时候让其修改样式,RouterLink
有一个属性active-class
,值是一个类名,效果是当该链接激活的时候,会实现该类名的样式。
<template>
<div class="screen-view">
<!-- 导航区 -->
<div class="header">
<RouterLink to="/route1" active-class="activeClass">路由1</RouterLink>
<RouterLink to="/route2" active-class="activeClass">路由2</RouterLink>
<RouterLink to="/route3" active-class="activeClass">路由3</RouterLink>
</div>
<!-- 展示区 -->
<div class="container">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup></script>
<style scoped>
.screen-view {
height: 100%;
}
.header {
display: flex;
justify-content: space-around;
height: 30px;
margin-bottom: 10px;
}
a {
display: inline-block;
height: 30px;
width: 100px;
background-color: pink;
text-align: center;
color: #fff;
text-decoration: none;
border-radius: 10px;
line-height: 30px;
}
.container {
margin-left: 100px;
margin-right: 100px;
height: calc(100% - 40px);
box-sizing: border-box;
border: 5px solid #ccc;
border-radius: 10px;
height: 700px;
}
.activeClass {
background-color: yellowgreen;
text-align: center;
color: #fff;
}
</style>
而且这个RouterLine
和a
标签的样式是通用的,所以此时作用在a
标签的样式也可以直接给RouterLink
去用,这个时候就简单完成了一个导航栏的基本功能。不过实际项目中我们会用Element Ui
组件去实现这个RouterLink
导航栏效果,但是该配置的路由信息还是要配置的,RouterView
也是要写上的。
哦对了,这个RouterView
和RouterLink
也是不用引入的,这里写引入只是为了知道这两个东西从哪里来的。
路由器的工作模式
我们前面讲配置路由器的时候要配置一个属性叫做history
,这是代表路由器的工作模式,路由器有两个工作模式,一个是history
(这里是工作模式的名称而非前面说的属性),另外一个叫做hash
。
history模式
history
模式是通过从vue-router
引入的createWebHistory
函数配置,这一点前面内容讲了。
// 创建一个路由器并且暴露出去
// 第一步:引入 vue-router
import { createRouter, createWebHistory } from 'vue-router'
// 第二步:创建一个路由器
const router = createRouter({
// 第三步:配置路由器
history: createWebHistory(), // 路由器的工作模式
routes: [ // 一个一个的路由信息
...
]
})
export default router
history
模式的优点就是url
更加美观,不会携带#
,更接近传统网站的url
。
我们来截个图看一下。
没有携带#
,原本url
是http://localhost:5173
,选择路由以后直接在后面拼接上http://localhost:5173/route1
,就看着比较美观。
但是history
工作模式有一个问题,项目后期上线以后,需要服务端配合处理路径问题,否则刷新会有404
报错。(以后会讲)
hash模式
hash
模式是通过从vue-router
引入的createWebHashHistory
函数配置。
// 创建一个路由器并且暴露出去
// 第一步:引入 vue-router
import { createRouter, createWebHashHistory } from 'vue-router'
// 第二步:创建一个路由器
const router = createRouter({
// 第三步:配置路由器
history: createWebHashHistory(), // 路由器的工作模式
routes: [ // 一个一个的路由信息
...
]
})
export default router
hash
模式的优点是不需要服务器去处理路径,兼容性更好。但是问题也是有的,就是url
上面会有一个#
,比较影响美观。
原本url
是http://localhost:5173/#/
,选择路由以后直接在后面拼接上http://localhost:5173/#/route1
,因为有#
所以看着没那么美观,而且在SEO
优化上比较差。
RouterLink中to属性的两种写法
字符串写法
我们前面在讲路由器的基本使用的时候,用RouterLink
进行路由的切换,去展示to
属性指定的路由路径所对应的组件内容,那时候我们的to
属性值是一个字符串,指的是路由信息的路径。
<RouterLink to="route1" active-class="activeClass">路由1</RouterLink>
切换到此路由的时候,RouterView
就会展示该路径所对应的组件内容。
对象写法
其实to
属性还有一种写法,这种写法下的to
属性的值是一个对象,里面其中有一个属性叫做path
,这个path
和配置路由时候的path
对应,这个path
的值也是一个字符串,指的就是路由的路径。
<RouterLink :to="{ path: '/route1' }" active-class="activeClass">路由1</RouterLink>
此时切换到该路由的时候也会展示该路由路径对应的组件内容。
这么一看是不是觉得还是字符串写法比较方便,其实不然,对象的写法里面可以做很多操作,比如给路由传参,或者设置动态路由,这一点我们下面会讲到。
命名路由
命名路由就是给路由去命名,通过路由的名称去展示路由对应的组件内容。
我们前面讲配置路由器的路由信息routes
的时候,讲了routes
是一个数组,里面的每一项都是一个对象,指的是不同的路由信息,对象里面有三个最重要的属性分别为path
,name
,component
,其中path
是路由的路径,name
是路由的名称,component
是路由路径对应的组件内容,path
和component
我们都讲了它的作用了,那name
的作用是什么呢?其实就是通过路由的名称去展示路由对应的内容。
const router = createRouter({
// 第三步:配置路由器
history: createWebHashHistory(), // 路由器的工作模式
routes: [ // 一个一个的路由信息
{
path: '/route1', // 路由路径
name: "routeName1",
component: () => import("@/views/route1/index.vue"), // 路由路径对应的组件内容
},
]
})
我们讲to
属性的对象写法的时候,不是觉得对象写法比字符串写法要麻烦吗,其实这里就可以用到name
属性了,这个时候我们不一样非要写{path: '/route1'}
才可以展示route1
路由对应的组件内容,可以用路由的name
属性。to
属性的对象值里也有一个name
属性,和路由的name
属性是对应的,就是路由的名称,此时切换到该路由的时候就会展示该路由的名称所对应的组件内容。
<RouterLink :to="{ name: 'routeName2' }" active-class="activeClass">路由2</RouterLink>
嵌套路由
我们前面做所的页面分为导航区和展示区,其中蓝色框里的是导航区,红色框里的是展示区,根据选择导航区里不同的路由,展示区会展示路由对应的内容。
那如果我们想在展示区里面再搞一个导航,再搞一个展示区呢?这个时候该怎么做,我先在route1
的组件内容里面去搭建这么一个情形。
route1/index.vue
<template>
<div class="screen-view">
<!-- 嵌套的导航区 -->
<div class="left-navmenu">导航区</div>
<!-- 嵌套的展示区 -->
<div class="right-container">展示区</div>
</div>
</template>
<script lang="ts" setup></script>
<style scoped>
.screen-view {
display: flex;
height: 100%;
}
.left-navmenu {
width: 260px;
height: 100%;
margin-right: 10px;
}
.right-container {
width: calc(100% - 270px);
height: 100%;
}
</style>
当前页面为下面这样。
我们想在route1
的页面里嵌套路由,就要用到路由信息配置的另外一个属性children
,该属性是一个数组,里面存放的跟routes
一样都是路由信息,只不过是该页面下嵌套的路由信息。
注意事项:这里嵌套的路由的路径path
不需要写/
了,到时候拼接在父路由的后面的时候会自己加上/
,如果这里写/
,就不是拼接在父路由后面,而是直接替换父路由了。
const router = createRouter({
// 第三步:配置路由器
history: createWebHashHistory(), // 路由器的工作模式
routes: [ // 一个一个的路由信息
{
path: '/route1',
name: "routeName1",
component: () => import("@/views/route1/index.vue"),
children: [
{
path: 'news1', // 嵌套的路由不需要写 /
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue')
},
{
path: 'news2',
name: 'newsName2',
component: () => import('@/views/route1/news2/index.vue')
},
{
path: 'news3',
name: 'newsName3',
component: () => import('@/views/route1/news3/index.vue')
},
]
},
{
path: '/route2',
name: "routeName2",
component: () => import("@/views/route2/index.vue"),
},
{
path: '/route3',
name: "routeName3",
component: () => import("@/views/route3/index.vue"),
},
]
})
然后我们再去创建嵌套路由对应展示的组件。
然后用RouterLink
和RouterView
去配置嵌套路由的跳转链接和展示区,此时RouterLink
的to
属性该怎么写呢?是写/news1
吗?不能这么写嗷,应该写/route1/news1
,如果直接写/news1
,就会把route1
给覆盖掉,相当于url
为http://localhost:5173/#/news1
了。
<template>
<div class="screen-view">
<!-- 嵌套的导航区 -->
<div class="left-navmenu">
<RouterLink to="/route1/news1" active-class="activeClass">新闻1</RouterLink>
<RouterLink to="/route1/news2" active-class="activeClass">新闻2</RouterLink>
<RouterLink to="/route1/news3" active-class="activeClass">新闻3</RouterLink>
</div>
<!-- 嵌套的展示区 -->
<div class="right-container">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup></script>
<style scoped>
.screen-view {
display: flex;
height: 100%;
}
.left-navmenu {
width: 260px;
height: 100%;
margin-right: 10px;
display: flex;
flex-direction: column;
justify-content: space-around;
}
a {
display: inline-block;
height: 30px;
width: 100px;
background-color: pink;
text-align: center;
color: #fff;
text-decoration: none;
border-radius: 10px;
line-height: 30px;
}
.activeClass {
background-color: yellowgreen;
text-align: center;
color: #fff;
}
.right-container {
width: calc(100% - 270px);
height: 100%;
}
</style>
这样嵌套路由的基本功能就实现了。
路由传参
路由也是可以传参数过去的,到时候跳转到新页面的时候会自动在url
后面拼接上这个参数。
路由传参有两种形式,一种是query
传参,还有一种是params
传参。
query传参
如果我们RouterLink
的to
属性值的写法是字符串形式的,就直接在后面用?
拼接上我们要传的参数,如果多个参数就用&
隔开,这个都懂吧,就是url
后面参数的写法。
<RouterLink to="/route1/news1?a=1&b=2&c=3" active-class="activeClass">嵌套路由1</RouterLink>
这个时候跳转到该页面的时候,url
上面就会拼接上我们传的参数。
传完参数以后我们在这个页面怎么接收这个参数呢?前面讲配置路由器的时候说了,router
是路由器,里面是整个路由器的信息。route
是路由,里面是该路由的所有信息。所以我们这里要接收路由上的参数,只要从route
上面拿就可以了,我们先从vue-router
上面引入一个useRoute
函数,我们执行这个函数并把它赋值给一个变量打印来看看是什么东西。
route1/news1/index.vue
<template>
<div>新闻内容1</div>
</template>
<script lang="ts" setup>
import { useRoute } from "vue-router";
let route = useRoute();
console.log("route", route);
</script>
可以看到打印出来的是一个Proxy
对象,这个是不是很熟悉,就是reactive
函数创建的复杂类型响应式数据,里面的Target
属性就是对象具体的值,这个对象就是路由信息对象route
,里面都是该路由的信息,我们可以看到里面有一个query
属性,这里面就是我们要接收的query
传参传来的参数。
所以我们可以直接通过route.query
的方式获取到传来的参数。
这是to
属性值为字符串写法的时候query
传参方式,如果参数很多的时候我们可能就要写好长一串,如果再传变量的话,加上反引号,变量名什么的就更长了,就很不美观。
<RouterLink :to="`/route1/test1?a=${a}&b=${b}&c=${c}`" active-class="activeClass">嵌套路由1</RouterLink>
所以一般传参的情况下,我们就会用到to
属性值为对象的写法。我们前面在讲to
属性值为对象的时候,讲过两个属性,一个是path
属性,指的是路由路径,点击该路由的时候就会展示到路径对应的组件内容,一个是name
属性,指的是路由名称,点击该路由的时候就会展示到路由名词对应的组件内容,现在再讲一个属性query
,这个属性值为一个对象,里面存放的就是我们query
传参要传的参数。
<RouterLink
:to="{ path: '/route1/test1', query: { a: 1, b: 2, c: 3 } }"
active-class="activeClass"
>嵌套路由1</RouterLink
>
这样看起来就比较简单明了了。
params传参
讲完query
传参我们现在来讲params
传参,还是先用to
属性值为字符串的写法来讲。
我们前面讲query
传参的时候,参数都是键值对的形式且用&
隔开,但是params
传参的时候就不用写键了,直接写值,如果多个就用/
隔开。
<RouterLink to="/route1/news1/yueliang/月亮/18" active-class="activeClass">嵌套路由1</RouterLink>
这样看着是不是比query
传参好一点,最起码不用写键了,但是这么写的话,这些参数是不是很像路由的路径啊,就像是子路由一样,我们在页面上看一下会有什么反应。
我们会发现刚进入父路由还没有点击子路由的时候,控制台就有警告了No match found for location with path "/route1/news1/yueliang/月亮/18"
,翻译过来就是没有找到/route1/news1/yueliang/月亮/18
这个路由,而且当我们点击这个子路由的时候,也没有展示对应的页面。
看起来路由器这里也是把传的参数当成路径去看了,所以要想这么传参,我们还得去路由信息那里做一些配置。
我们需要在被传参的路由上,这里也就是子路由news1
上做一些修改,在这个路由的路径属性path
值后面通过/
去拼接一些东西,这个东西就是刚才我们传参的键了,而且为了防止路由器觉得拼接的是路径,我们还得在键前面加上:
。
router/index.ts
const router = createRouter({
// 第三步:配置路由器
history: createWebHashHistory(), // 路由器的工作模式
routes: [ // 一个一个的路由信息
{
path: '/route1',
name: "routeName1",
component: () => import("@/views/route1/index.vue"),
children: [
{
path: 'news1/:enName/:cnName/:age', // 被传参的路由路径后面拼接上要传参的键
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue')
},
{
path: 'news2',
name: 'newsName2',
component: () => import('@/views/route1/news2/index.vue')
},
{
path: 'news3',
name: 'newsName3',
component: () => import('@/views/route1/news3/index.vue')
},
]
},
{
path: '/route2',
name: "routeName2",
component: () => import("@/views/route2/index.vue"),
},
{
path: '/route3',
name: "routeName3",
component: () => import("@/views/route3/index.vue"),
},
]
})
这个时候页面就是可以正常展示的了,这就相当于传参的时候在RouterLink
的to
属性上只写参数的值,而把参数的名称写在配置路由信息的地方。
讲完传参,现在讲讲如何取参数,我们在讲取query
传的参数的时候,是用到了useRoute
函数的,它的返回值就是该路由的信息对象,里面存放着该路由的所有信息,当时打印这个路由信息对象的时候,里面其中一个属性是query
,这就是query
传的参数,还有一个属性params
,这里就是params
传的参数了,属性params
也是一个对象,里面就是参数的键值对。
route1/news1/index.vue
<template>
<div>新闻内容1</div>
</template>
<script lang="ts" setup>
import { useRoute } from "vue-router";
let route = useRoute();
console.log("route", route);
</script>
讲完to
属性值是字符串的情况,就要讲to
属性值为对象的情况了,还是一样,因为如果传参太多且是变量的话,字符串的形式就要写好长了。
<RouterLink :to="`/route1/news1/${enName}/${cnName}/${age}`" active-class="activeClass">嵌套路由1</RouterLink>
所以用对象的写法,这样可读性也比较高,讲到现在也应该猜得出来对象的写法怎么写了,query
传参的时候用的是to
属性值对象的query
属性,那么params
传参的时候自然要用的就是to
属性值对象的params
属性了。按照query
传参的写法就是to
属性值里面有一个path
属性存放路径,一个params
属性存放参数的键值对,但是事实真的如此吗?
<RouterLink
:to="{ path: '/route1/news1', params: { enName: 'yueliang', cnName: '月亮', age: 18 }}"
active-class="activeClass"
>嵌套路由1</RouterLink
>
会发现当我们点击子路由后,页面没展示了,而且url
上面也没有参数了,后台还告警了Path "/route1/news1" was passed with params but they will be ignored. Use a named route alongside params instead
,这句话的意思是,当我们用params
传参的时候会忽略path
,要使用命名路由。所以此时的写法应该是:
<RouterLink
:to="{ name: 'newsName1', params: { enName: 'yueliang', cnName: '月亮', age: 18 }}"
active-class="activeClass"
>嵌套路由1</RouterLink
>
此时页面就可以正常展示了,而且url
后面也会拼接上参数。
这里还有两个个注意点:1.params传参的时候,不能传对象或数组,会报错。2.如果我们用to
属性值为对象的写法,然后又不配置路由信息,就是说在router/index.ts文件上配置路由路径的时候不在后面拼接上参数的键,页面可以正常展示,但是不会传参,如果to
属性值为字符串,就会直接报错,因为路由器会把参数当成路径去分析,这个前面讲过了, 这里就不展示了,可以自己去试一试。
现在还有一个情况,就是如果我突然不想传某个参数了呢?我们试一下,不传参数age
。
<RouterLink
:to="{ name: 'newsName1', params: { enName: 'yueliang', cnName: '月亮' }}"
active-class="activeClass"
>嵌套路由1</RouterLink
>
页面会直接报错,说你缺少一个必要的参数age
,如果遇到这种情况,想实现这种可传可不传的参数的时候,我们只需要在配置路由信息的时候,在给路径后面拼接参数名的时候,在后面加上一个?
就可以了,像ts
那样。
router/index.ts
const router = createRouter({
// 第三步:配置路由器
history: createWebHashHistory(), // 路由器的工作模式
routes: [ // 一个一个的路由信息
{
path: '/route1',
name: "routeName1",
component: () => import("@/views/route1/index.vue"),
children: [
{
path: 'news1/:enName/:cnName/:age?', // 被传参的路由路径后面拼接上要传参的键
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue')
},
{
path: 'news2',
name: 'newsName2',
component: () => import('@/views/route1/news2/index.vue')
},
{
path: 'news3',
name: 'newsName3',
component: () => import('@/views/route1/news3/index.vue')
},
]
},
{
path: '/route2',
name: "routeName2",
component: () => import("@/views/route2/index.vue"),
},
{
path: '/route3',
name: "routeName3",
component: () => import("@/views/route3/index.vue"),
},
]
})
这个时候就可以正常展示页面了。
区别
params
传参的时候不能用路径path
去展示页面,只能用命名路由的方式,并且不能传数组或者对象,而query
传参的时候没有这些限制。而且params
传参需要在配置路由信息的时候提前在规则中占位。
路由的props配置
这里的props
配置可不是父子组件传值了,指的是路由规则的props
配置。它有三个写法:
1.布尔值写法
我们前面光讲怎么传参了,但是还没有用,所以我们现在修改下代码让其用到我们传的参数。
route1/index.vue 父页面
这里修改了一下英文名,不用拼音了,好丢人......
<template>
<div class="screen-view">
<!-- 嵌套的导航区 -->
<div class="left-navmenu">
<RouterLink
:to="{ name: 'newsName1', params: { enName: 'Moon', cnName: '月亮', age: 18 } }"
active-class="activeClass"
>嵌套路由1</RouterLink
>
<RouterLink to="/route1/news2" active-class="activeClass">嵌套路由2</RouterLink>
<RouterLink to="/route1/news3" active-class="activeClass">嵌套路由3</RouterLink>
</div>
<!-- 嵌套的展示区 -->
<div class="right-container">
<RouterView></RouterView>
</div>
</div>
</template>
route1/news1/index 子页面
接收参数以后在页面上进行展示:
<template>
<div>中文:{{ route.params.cnName }}</div>
<div>英文:{{ route.params.enName }}</div>
<div>年龄:{{ route.params.age }}</div>
</template>
<script lang="ts" setup>
import { useRoute } from "vue-router";
let route = useRoute();
console.log(route);
</script>
可以看到页面是可以正常展示的,但是这样用起来要写好长一串就比较麻烦,下面还有配置一堆东西,所以这里就可以用到路由的props
配置了。
既然是路由规则的props
配置,那自然配置在路由信息上,我们在讲配置路由信息的时候,已经讲了path
,name
,component
了,现在又多了一个props
,第一个写法是布尔值写法,所以props
的值为一个布尔值。
router/index.ts
const router = createRouter({
// 第三步:配置路由器
history: createWebHashHistory(), // 路由器的工作模式
routes: [ // 一个一个的路由信息
{
path: '/route1',
name: "routeName1",
component: () => import("@/views/route1/index.vue"),
children: [
{
path: 'news1/:cnName/:age/:enName',
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue'),
props: true
},
{
path: 'news2',
name: 'newsName2',
component: () => import('@/views/route1/news2/index.vue')
},
{
path: 'news3',
name: 'newsName3',
component: () => import('@/views/route1/news3/index.vue')
},
]
},
{
path: '/route2',
name: "routeName2",
component: () => import("@/views/route2/index.vue"),
},
{
path: '/route3',
name: "routeName3",
component: () => import("@/views/route3/index.vue"),
},
]
})
加了props
为true
以后就相当于什么,相当于父组件route1
给子组件news1
传值cnName
,enName
,age
了,那我们就可以直接在子组件news1
上用defineProps
函数去接收值了。这一点我们在讲父子组件传值的时候已经讲过了。
route1/news1/index.vue
<template>
<div>中文:{{ cnName }}</div>
<div>英文:{{ enName }}</div>
<div>年龄:{{ age }}</div>
</template>
<script lang="ts" setup>
defineProps(["cnName", "enName", "age"]);
</script>
此时页面也可以正常展示,但是这个用法只限制params
传参。
我们改成query
传参看一下:
router/index.ts
{
path: 'news1',
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue'),
props: true
},
route1/news1/index.vue
<RouterLink
:to="{ name: 'newsName1', query: { enName: 'Moon', cnName: '月亮', age: 18 } }"
active-class="activeClass"
>嵌套路由1</RouterLink
>
可以看到页面是没有展示了。所以这种props
为true
的用法只能用于params
传参。
2.函数写法
那怎么用到query
传参呢?这就用到第二种写法函数写法了,此时props
的值不再是一个布尔值true
了,而是要把props
写成一个函数,然后返回一个对象,对象里的值会作为props
传给路由组件。
router/index.ts
{
path: 'news1',
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue'),
props() {
return { a: 1, b: 2, c: 3 }
}
},
此时我们就可以用defineProps
去接收这些参数了。
route1/news1/index.vue
<template>
<div>中文:{{ a }}</div>
<div>英文:{{ b }}</div>
<div>年龄:{{ c }}</div>
</template>
<script lang="ts" setup>
defineProps(["a", "b", "c"]);
</script>
此时页面上也可以正常展示,但是这跟我们说的query
传参有什么关系呢?其实props
函数接收一个参数,我起名为route
,因为这个参数route
就是我们的路由信息对象。
router/index.ts
{
path: 'news1',
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue'),
props(route) {
console.log(route);
return { a: 1, b: 2, c: 3 }
}
},
因为props
函数返回一个对象,对象里的值会作为props
去传给路由组件,而query
属性又正好是一个对象,所以我们可以直接把query
给return
出去。
router/index.ts
{
path: 'news1',
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue'),
props(route) {
return route.query
}
},
route1/news1/index.vue
<template>
<div>中文:{{ cnName }}</div>
<div>英文:{{ enName }}</div>
<div>年龄:{{ age }}</div>
</template>
<script lang="ts" setup>
defineProps(["cnName", "enName", "age"]);
</script>
此时页面上也可以正常展示了,所以用函数写法,就可以把query
传的参数或者其他路由信息作为props
传给路由组件。
3.对象写法
对象写法就像我们刚才讲函数写法的时候一样,把这个对象里的属性作为props
传给路由组件,只是不接收路由信息参数。所以对象写法相当于阉割版的函数写法,不怎么用。
router/index.ts
{
path: 'news1',
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue'),
props: {
a: 100,
b: 200,
c: 300
}
},
路由的replace属性
路由跳转的时候会操纵浏览器的历史记录,你跳转过到哪些页面浏览器的历史记录上面都会有。路由操作浏览器的历史记录有两个动作,一个叫做push
,另外一个叫做replace
。
push
是什么意思呢,浏览器有一个栈专门存放浏览器的历史记录,每当我们浏览一个新页面的时候,都会往栈的最上面存放一个页面地址,旧的地址会在新地址的下面,就相当于有一个小指针,一直指着栈的最上面的页面地址,这就是push
模式,一个一个把跳转过的页面推入到栈里,栈里会存放跳转过的所有历史记录,所以浏览器那个->
和<-
就可以操控页面前进还是后退,就像是调整指针的位置。
而replace
不是一个一个往上堆,它是直接用新的页面去覆盖旧的页面,这个时候浏览器那个->
和<-
就无法后退找到原本的旧页面,就更不可能往前进,因为栈里就只有当前页面一条历史记录,指针只能指向这个位置,这就是replace
模式。
默认的模式是push
,但是我们可以给改成replace
,操作很简单,就是在RouterLink
组件上添加replace
属性就好了。
<template>
<div class="screen-view">
<!-- 嵌套的导航区 -->
<div class="left-navmenu">
<RouterLink
replace
:to="{ name: 'newsName1', query: { enName: 'Moon', cnName: '月亮', age: 18 } }"
active-class="activeClass"
>嵌套路由1</RouterLink
>
<RouterLink replace to="/route1/news2" active-class="activeClass">嵌套路由2</RouterLink>
<RouterLink replace to="/route1/news3" active-class="activeClass">嵌套路由3</RouterLink>
</div>
<!-- 嵌套的展示区 -->
<div class="right-container">
<RouterView></RouterView>
</div>
</div>
</template>
编程式路由导航
我们到现在为止,所有的导航区都是通过RouterLink
写的,但是RouterLink
是一个组件,浏览器根本不认识它,所以最终它会解析成一个HTML
标签中的a
标签。
那如果我现在不想通过RouterLink
去跳转路由该怎么办呢,比如我们点击某个图片然后跳转到相关页面,或者说执行完一个方法以后跳转到某个页面,再或者说我想3s后自动跳转页面,这该怎么做呢,上面的操作肯定是在js
或者ts
里面通过方法进行操作的,比如点击事件或者监视器,那RouterLink
总不能写在ts
里面吧。
而这种不通过RouterLink
组件或者说RouterLink
形成的a
标签进行页面跳转的方法就叫做编程式路由导航。那这种方法该怎么实现呢。
我们前面说了route
是路由,存放着某个路由的信息,我们通过这个获取过url
上的参数,而router
是路由器,上面存放着整个路由器的相关信息和操作,所以我们可以通过router
进行页面跳转。route
是useRoute
函数的执行返回,那么router
自然就是useRouter
函数的执行返回了,我们来打印一下看看有什么东西。
route1/index.vue
<template>
<div class="screen-view">
<!-- 嵌套的导航区 -->
<div class="left-navmenu">
<RouterLink
replace
:to="{ name: 'newsName1', query: { enName: 'Moon', cnName: '月亮', age: 18 } }"
active-class="activeClass"
>嵌套路由1</RouterLink
>
<RouterLink replace to="/route1/news2" active-class="activeClass">嵌套路由2</RouterLink>
<RouterLink replace to="/route1/news3" active-class="activeClass">嵌套路由3</RouterLink>
</div>
<!-- 嵌套的展示区 -->
<div class="right-container">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from "vue-router";
let router = useRouter();
console.log(router);
</script>
其中有一个push
方法和一个replace
方法,需要注意一下,这两个方法就是进行跳转页面的,而这两个方法的不同之处就是模式不一样,对应着我们前面说的浏览器历史记录上的push
模式和replace
模式。
这两个方法怎么用呢?这里我举个例子,假如刚进入route1
页面3s以后就自动跳转页面到news1
,就可以直接使用push
方法或者replace
方法,然后携带一个参数,就是news1
的路由路径。
route1/index.vue
<template>
<div class="screen-view">
<!-- 嵌套的导航区 -->
<div class="left-navmenu">
<RouterLink
replace
:to="{ name: 'newsName1', query: { enName: 'Moon', cnName: '月亮', age: 18 } }"
active-class="activeClass"
>嵌套路由1</RouterLink
>
<RouterLink replace to="/route1/news2" active-class="activeClass">嵌套路由2</RouterLink>
<RouterLink replace to="/route1/news3" active-class="activeClass">嵌套路由3</RouterLink>
</div>
<!-- 嵌套的展示区 -->
<div class="right-container">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from "vue-router";
let router = useRouter();
setTimeout(() => {
router.push('/route1/news1')
}, 3000);
</script>
push
方法或者replace
方法是用来跳转路由的,而RouterLink
组件中to
也是跳转路由的,所以push
方法或者replace
方法的参数,写法和RouterLink
组件中to
的写法是一致的,可以直接写字符串,也可以写对象。
// 字符串写法
setTimeout(() => {
router.push("/route1/news1");
}, 3000);
// 对象写法
setTimeout(() => {
router.push({
path: "/route1/news1",
query: {
a: 1,
b: 2,
},
});
}, 3000);
编程式路由导航通过router
可以进行很多操作,比如它的back
方法,就是回退到上一个页面,go
方法是前进到前面的页面,还有其他的这里就不一一赘述了。
路由的重定向
页面刚打开的时候路由的路径就是/
,像下面这样。
这个时候一般来说是不会展示任何东西的,为什么我们这里展示了是因为这只是一个测试案例,一般情况下刚进入项目的时候是不会展示任何东西的,此时体验就不会很好。
而路由如果设置了重定向,当页面的url
上面的路径是该路由的路径的时候,就会给它重新跳转到别的页面上面去,所以此时我们就可以给/
设置一个重定向,让其刚进入页面的时候就跳转到别的页面去,就不会有空白。
router/index.ts
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
redirect: '/route1' // 重定向
},
{
path: '/route1',
name: "routeName1",
component: () => import("@/views/route1/index.vue"),
children: [
{
path: 'news1',
name: 'newsName1',
component: () => import('@/views/route1/news1/index.vue'),
props(route) {
return route.query
}
},
{
path: 'news2',
name: 'newsName2',
component: () => import('@/views/route1/news2/index.vue')
},
{
path: 'news3',
name: 'newsName3',
component: () => import('@/views/route1/news3/index.vue')
},
]
},
{
path: '/route2',
name: "routeName2",
component: () => import("@/views/route2/index.vue"),
},
{
path: '/route3',
name: "routeName3",
component: () => import("@/views/route3/index.vue"),
},
]
})
此时刚打开项目的时候url
上的路径是/
,因为给该路由设置了重定向(/ 其实也是一个路由),所以就会自动跳转到重定向指定的页面。
这个功能项目很常用,一般来说都是用于刚进入页面的时候跳转到登录页的。