Vue3 使用路由 Router
之前几篇博文说了一下 vue 的基本语法和 vue 的传参,今天这篇博文稍微说一下 vue3 里面使用路由。
介绍
众所周知,vue 是用来构建单页面应用的前端框架,大于大多数此类型应用来讲,都推荐使用官方支持的 vue Router,在单页面应用,客户端的 JavaScript 可以连接页面跳转请求,动态获取数据,然后无需重新加载页面的情况下,更新当前页面数据,这样可以带来更加丝滑的用户体验,因为这类场景下的用户通常会在很长的一段时间中做出多次交互,路由是更新在客户端执行的。
vue Router
是 vue 官方路由,他与 vue 核心深度集成,让 vue 构建单页面应用变得更加轻而易举。
- 嵌套路由映射
- 动态路由选择 模块化、基于组件的路由配置
- 路由参数、查询、通配符
- 展示由 Vue.js 的过渡系统提供的过渡效果
- 细致的导航控制
- 自动激活 CSS 类的链接
- HTML5 history 模式或 hash 模式
- 可定制的滚动行为
- URL 的正确编码
router 安装
安装 vue Router 只需要一个简单的命令即可实现安装:
bash
npm install vue-router -S
执行完成之后,只需要静待安装完成即可。
安装完成之后,我们可以看到已经装了 4 版本的 router,如果是 vue2 的项目,则需要安装 3 版本的。
因为这两个版本他们是不互相兼容的,代码是不一样的,切记。
router 初始化
首先我们在 src
文件夹下创建一个 router
文件夹,在内部创建一个 index.ts
文件。
首先我们需要在这个 index.ts
文件中引入 router:
typescript
import { createRouter } from "vue-router";
然后我们初始化一下路由:
typescript
import { RouteRecordRaw, createWebHistory, createRouter } from "vue-router";
const routes: Array<RouteRecordRaw> = [
{
path: '/',
component: () => import('../components/HelloWorld.vue') // 首页组件
}, {
path: '/about',
component: () => import('../components/About.vue') // 关于我们组件
}
]
const router = createRouter({
history: createWebHistory(), // 路由类型
routes // short for `routes: routes`
})
export default router
然后,我们需要在 main.ts 文件中注册一下子:
bash
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
这样的话,我们就把路由集成进项目了。
router-view
我们刷新一下页面,发现并没有任何效果,为啥子呢?
其实到这一步,我们已经将路由添加到项目里面去了,但是没有效果,是因为我们还没有写一个容器来引入我们的路由。
接下来我们写一个容器,在 App.vue 项目里面:
html
<script setup>
</script>
<template>
<h1 class="ed-t">我是ed. vue3+router</h1>
<router-view></router-view>
</template>
<style scoped>
.ed-t {
padding: 10px;
margin: 5px;
color: hotpink;
}
</style>
像 vue2 项目一样,使用 <router-view></router-view>
插入路由。
这样的话我们刷新页面,可以看到我们能够根据路由变化切换组件更新显示内容:
注意: <router-view></router-view>
可以卸载任何位置,这个根据实际业务的排版来就可以。
router-link
接下来说一下 router-link
,这个是和 vue2 完全一样的,我们在 App.vue 文件编写 router-link
。
html
<template>
<h1 class="ed-t">我是ed. vue3+router</h1>
<div>
<router-link class="ed-rl" to="/">首页</router-link>
<router-link class="ed-rl" to="/about">关于我们</router-link>
</div>
<router-view></router-view>
</template>
这样的话,我们点击 router-link
的时候,可以快速实现组件切换,注意 router-link
必须有一个 to
属性,to
属性的值必须与初始化的 router
里面的 path
对应,意味着去哪个页面。
我们点击之后看到,下面的组件切换了,同时地址栏的地址也修改掉了。这就是路由最简单的使用方式。
路由模式
接下来说一下路由模式:
vue2 | vue3 |
---|---|
history | createWebHistory |
hash | createWebHashHistory |
abstact | createMemoryHistory |
上面是 vue2 和 vue3 路由类型的对比,其中 vue2 配置类型使用的属性是 mode
, vue3 里面更新为 history
。
createWebHashHistory
上面的案例我们使用了 createWebHistory
模式:
看到访问的路由就是正常类似于多页面的地址。
但是如果使用了 createWebHashHistory
模式之后:
typescript
const router = createRouter({
history: createWebHashHistory(),
routes // short for `routes: routes`
})
我们看一下:
地址中间使用了 #
连接。
他是通过 location.hash
去匹配路由的:
比如我们让他跳转到首页:
就是这个样子。监听浏览器左右箭头,是使用一个回调函数实现的:
我们切换浏览器左右箭头就会触发打印:
createWebHistory
使用 createWebHistory
在地址栏是没有 #
号的。
它是基于 H5
的 history
实现的:
它监听浏览器左右箭头是通过 popstate
实现的:
这时候,我们切换浏览器前后箭头,就可以打印出数据:
好的,就是这个样子。跳转的话是使用 pushState
实现跳转的:
使用这个切换了之后,你会发现页面地址栏地址已经变了,但是页面并没有修改,这是因为,你这种方式切换并不会监听到,还是需要手动刷新页面。
编程式导航
path 跳转
html
<template>
<h1 class="ed-t">我是ed. vue3+router</h1>
<div>
<router-link class="ed-rl" to="/">首页</router-link>
<router-link class="ed-rl" to="/about">关于我们</router-link>
</div>
<router-view></router-view>
</template>
上面的案例,我们是使用 router-link
标签通过 path
方式实现的路由跳转,除了使用 path 实现路由跳转之外,我们还可以使用 name
的方式进行路由的跳转。
name 跳转
比如我们给 routes 列表的路由配置添加一个名字:
typescript
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: "index",
component: () => import('../components/HelloWorld.vue')
}, {
path: '/about',
name: "about",
component: () => import('../components/About.vue')
}
]
注意哈,这个 name
不要起重复了嗷!
然后我们修改一下 router-link
标签,由 path
跳转改为 name
跳转:
html
<div>
<router-link class="ed-rl" to="/">首页</router-link>
<router-link class="ed-rl" :to="{name: 'about'}">关于我们</router-link>
</div>
我把 关于我们 改成通过 name 跳转了,可以对比一下子,效果一样一样滴!
a 标签跳转
我们还可以使用另一种方式,就是直接是 a
标签:
html
<a href="/">首页</a>
<a href="/about">关于我们</a>
但是这个和 path 跳转还是有区别的:
这个动图可能看不太清楚,我说一下吧,注意浏览器刷新按钮,我们切换页面的时候,按钮编程叉号一段时间,所以可以说明,使用这个方式实现页面跳转的话,会看到页面整体闪烁了一下子,他是整个页面给你刷新,而不是其中一部分刷新。因此不建议使用这种方式,只是知道就可以了。
编程式跳转
编程式跳转就是不通过便签实现路由的跳转,而是使用 js 代码的方式实现,用于我们点击按钮,手动进行跳转,或者是点击按钮,进行一些逻辑处理后在进行跳转。
那么我们可以在之前写 a 标签的地方改成两个按钮吧:
html
<button class="ed-rl">首页</button>
<button class="ed-rl">关于我们</button>
然后呢,我们给按钮添加个点击事件:
typescript
<button @click="toPage('/')" class="ed-rl">首页</button>
<button @click="toPage('/about')" class="ed-rl">关于我们</button>
然后我们写一下这个 toPage
事件:
typescript
<script setup lang="ts">
// 引入 hook
import { useRouter } from 'vue-router'
// 初始化一下
const router = useRouter()
// 页面跳转方法
const toPage = (url: string) => {
// 跳转页面
router.push(url)
}
</script>
这样的话,我们就是先了一个简单的编程式路由跳转:
效果是一样的。
router.push(url)
不仅仅可以传路由,他还可以传递一个对象,比如说用来跳转传参:
typescript
router.push({path: url}) // path 跳转
也可以是用 name 进行跳转:
typescript
router.push({name: 'about'}) // name 跳转
效果一样就不截图了。
历史纪录
上面的案例哈,我们点击按钮跳转完之后,我们可以通过浏览器的前进、后退按钮实现对应的操作。
因为通过 vue 路由的操作,会把历史纪录给存储起来。
但是,有时候嘞,我操作完,也就是页面跳转完成之后,我不想把历史纪录给存储起来,就比如说,我登录完成之后,我不想点击浏览器后退按钮在进入登录页面,这是后怎么办呢?
router-link 标签
首先我们看 router-link
标签:
html
<router-link replace class="ed-rl" to="/">首页</router-link>
如果是 router-link
标签的话,我们可以直接使用一个 replace
设置这个路由不被保存到历史记录。
编程式开发
如果是使用编程式开发的话也很简单,就是把 push
改为 replace
即可:
typescript
router.replace(url) // path 跳转
效果是一样的,也是没有历史纪录,效果一样就不截图了。
历史纪录逻辑操作
关于历史纪录的逻辑处理也很简单。
typescript
<router-link class="ed-rl" to="/">首页</router-link>
<router-link class="ed-rl" :to="{ name: 'about' }">关于我们</router-link>
<button @click="prev()" class="ed-rl">向前</button>
<button @click="next()" class="ed-rl">向后</button>
我们不用浏览器,点击自己的自定义按钮实现向前、向后切换功能:
typescript
const prev = () => {
// router.go(-1) // 参数是后退几个历史,比如1个,2个。
router.back() // 后退
}
const next = () => {
router.go(1) // 参数是前进几个历史,比如1个,2个。
}
看一下效果:
都是没有问题的!
路由传参
路由传参是项目里面肯定会用的功能,所以说这个得好好整一下,下面这一节,主要说一下关于路由传参的部分。
案例准备
先准备一个案例,我随便写的,咱就不要细说了关于这个案例,首先准备一个电影列表的 json 文件:
json
{
"data": [
{
"name": "流浪地皮",
"price": 29.9,
"msg": "五星"
},
{
"name": "我就是潘金莲",
"price": 19.9,
"msg": "四星"
},
{
"name": "水壶传",
"price": 9.9,
"msg": "三星"
}
]
}
然后编写一个组件展示一下:
typescript
<template>
<p class="ed-title">电影列表</p>
<p class="ed-item" @click="toPage(item, index)" v-for="item, index in data" :key="index">No.{{ index + 1 }} - 《{{ item.name }}》</p>
</template>
<script setup lang="ts">
import { data } from '../json/data.json'
type Film = {
name: string,
msg: string,
price: number
}
// 点击电影项事件
const toPage = (item: Film , index: number) => {
// todo: 跳转到新的页面,展示详细数据
}
</script>
样式我就不粘贴了,我们现看一下效果:
我们点击电影名称,跳转到 about 页面,展示详细信息,这时候,我们在点击事件里面需要实现两个功能,第一个是跳转,第二个是传参:
首先我们需要引入 router :
typescript
import { useRouter} from 'vue-router'
因为引入进来的是 hook,我们需要调用一下:
typescript
const router = useRouter()
好的,我们实现页面跳转:
typescript
const toPage = (item: film, index: number) => {
router.push({
path: '/about',
})
}
好的,这样就实现了页面的跳转:
query 传参
然后,是传递参数,和 vue2 其实是一样一样的:
typescript
const toPage = (item: Film, index: number) => {
router.push({
path: '/about',
query: item,
})
}
注意,query
只能设置对象。上面我们是使用的 query
进行参数传递,
我们看到我们再次点击的时候,就会在地址栏显示我们传递的参数。
然后我们就可以在详情页面去取一下数据:
typescript
<template>
<p class="ed-title">电影详情</p>
<button @click="router.back()">返回</button>
<p class="ed-t">名称:{{ route.query.name }}</p>
<p class="ed-t">价格:¥{{ route.query.price }}</p>
<p class="ed-t">备注:{{ route.query.msg }}</p>
</template>
<script setup lang="ts">
import { useRoute} from 'vue-router';
import { useRouter } from 'vue-router';
const route = useRoute()
const router = useRouter()
</script>
<style scoped>
.ed-title {
font-size: 20px;
font-weight: 550;
color: rgb(6, 221, 236);
padding: 10px;
margin: 5px;
}
.ed-t {
font-size: 16px;
padding: 10px;
margin: 5px;
}
button {
margin: 15px;
}
</style>
然后,我们看一下效果:
效果实现了,可以传参并且展示出数据。
params 传参
注意 params
不能使用 path
进行参数传递,只能使用 name
进行传参。
所以修改上面页面跳转的代码:
typescript
const toPage = (item: Film, index: number) => {
// router.push({
// path: '/about',
// query: item,
// })
router.push({
name: 'about',
params: item,
})
}
上面代码就已经修改成 params
的方式进行参数传递了。params传参有一个特点,就是他传递的参数不会显示在地址栏:
看,点击之后,通过 params
传递参数的时候,地址栏已经不会显示传递的参数是什么了。
然后我们需要修改一下接受参数的地方,同样也是改为 params 接收参数:
typescript
<p class="ed-t">名称:{{ route.params.name }}</p>
<p class="ed-t">价格:¥{{ route.params.price }}</p>
<p class="ed-t">备注:{{ route.params.msg }}</p>
这样就可以实现数据显示了。
但是,注意一个问题:
啥意思哈,就是从 4.1.4
版本之后,修改了route.params.name
之后也显示不出来,会出问题:
因为新版本把这个功能给砍掉了,怎么办呢?看下面官方提供的平替解决方案:
嵌套路由
直接一个案例,和 vue2 完全一样,就不重复了:
typescript
import { RouteRecordRaw, createWebHashHistory, createWebHistory, createRouter } from "vue-router";
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: "footer",
component: () => import('../components/Footer.vue'),
children: [
{
path: '',
name: "index",
component: () => import('../components/HelloWorld.vue')
}, {
path: '/about',
name: "about",
component: () => import('../components/About.vue')
}
]
},
]
const router = createRouter({
history: createWebHistory(),
routes // short for `routes: routes`
})
export default router
命名视图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航)
和 main (主内容)
两个视图,这个时候命名视图就派上用场了。
你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default
。
typescript
<router-view name="LeftSidebar"></router-view>
<router-view></router-view>
<router-view name="RightSidebar"></router-vie
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s
):
typescript
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
不常用,了解即可。
路由重定向 redirect
重定向比较简单一笔带过:
typescript
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: "footer",
redirect: '/about',
children: [
{
path: '/index',
name: "index",
component: () => import('../components/HelloWorld.vue')
}, {
path: '/about',
name: "about",
component: () => import('../components/About.vue')
}
]
},
]
主要是使用的 redirect: '/about',
这一段代码实现重定向功能。
除了直接设置,还可以使用对象的方式实现重定向:
typescript
redirect: {
path: '/about'
},
可以设置 path ,当然设置 name 也是一样的:
typescript
redirect: {
name: 'about'
},
除了上面两种方式,还可以设置一个回调:
typescript
redirect: to => {
console.log(to)
return '/about'
},
回调的话,我们可以接受一个参数 to,我们打印了 to 的信息,同时他需要返回一个路径:
打印出了他父路由的信息,ok,没问题!除了返回一个路径之外,同样也是可以返回一个对象实现传参:
typescript
redirect: to => {
console.log(to)
return {
path: '/about',
query: {
name: "我是ed."
}
}
},
也是没有任何问题的,效果都一样。
OKOK,没有问题,参数也传递过去了!
路由别名 alias
alias 就是给我们的路由起多个名字,别名可以随便起,甚至可以取多个。
typescript
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: "footer",
alias: ['/footer', '/footer1', '/footer2'],
component: () => import('../components/Footer.vue')
},
]
我们给这个路由设置了多个别名,我们访问哪一个别名之后呢,都可以访问到这个路由:
上面我们测试了一下别名,没有任何问题。
导航守卫
typescript
router.beforeEach((to, from, next) => {
// to 要前往的页面;from 从哪个页面来;next() 设置到哪个页面
if(localStorage.getItem('token')) {
next(to.path)
}else {
next("/login")
}
})