vue学习记录之vue路由组件vue-router
前言
在使用前端框架之前链接其他页面我们可以使用<a>
标签来实现,但是使用了vue
等框架后,我们的源码都是各种.vue
文件,自然无法再使用<a>
标签进行跳转,如果要想使用<a>
标签实现则必须知道编译后的路径+名称,这种虽然能做到但是太过费事。为此Vue
官方为我们开发者提供了vue-router
来实现页面跳转,它的作用就跟<a>
标签类似。
安装
CDN使用
https://unpkg.com/vue-router@4
npm安装
npm i vue-router@4
一般在使用
vue-cli
创建项目时勾选vue-router
自动会生成示例代码.
快速开始
需要用到路由的页面
<template>
<router-link to="/"/>
<!-- 对于命名路由还可以通过名称跳转 -->
<router-link to="{name: 'home'}"/>
<router-link to="/hello"/>
<!-- 通过此标签展示跳转后的页面内容 -->
<router-view/>
</template>
对路由进行配置的JavaScript
import {createRouter, createWebHistory} from 'vue-router';
import HelloWorld from "@/components/HelloWorld";
const routes = [
{
name: 'Home', // 设置了name属性的路由被称为命名路由
path: '/',
// 路由懒加载(推荐)
component: () => {
import('@/components/Home')
}
},
{
name: 'HelloWorld',
path: 'hello',
// 常规加载
component: HelloWorld
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
export default router;
把上面这个配置在vue
对象中用上.
import { createApp } from 'vue'
import App from './App.vue'
// @是src的别名
import router from "@/router";
const app = createApp(App)
app.use(router)
app.mount('#app')
基础
路由参数获取
可以使用$route.query
或者$route.params
来获取。
$route.query
中存放的是URL
中的参数(/api/user?userId=123
)$route.params
中存放的是URL
中的路径参数(/api/user/123
,123
即为路径参数)
路由跳转
此处的路由跳转指的是通过
JavaScript
的方式实现跳转。
路由跳转可以使用$router.push()
函数实现,$router
中还提供了其他的实用函数,例如:$router.go()
、$router.replace()
等。
以下几个API就是模仿的window.history
的API,用的时候不会有太多陌生感。
$router.push({path: '', query: {}})
$router.go(1)
在历史记录中前进,与$router.forward()
等价$router.go(-1)
在历史记录中后退,与$router.back()
等价$router.push({path: '/home', replace: true})
替换当前位置(不会记录在历史记录中),它等价于$router.replace({path: '/home'})
。
// !!! 需要注意的是
$router.push({path: '', params: {}})
// 这种情况下params会被忽略,它们两个不能用在一起。可以使用下面两种替代
// 方式1
$router.push({name: 'home', params: {user: 123}})
// 方式2
$router.push({path: `/home/${user}`})
动态路由
SpringWebMVC
中有@RequestMapping
支持路径参数、正则语法,vue-router
中支持这样的使用。
const routes = [
// 动态段以冒号开始
{ path: '/users/:id', component: UserDemo },
]
这个路由能匹配到/users/abc
、/users/1234
等,其中abc
、1234
可以通过$route.params.id
获取到。一个路由上还可以设置多个路径参数。
路由 | 匹配路径 | $route.params |
---|---|---|
/users/:id | /user/abc | {id:abc} |
/users/:id/:type | /user/abc/1234 | {id:abc,type:1234} |
使用带有参数的路由时需要注意的是,当用户从
/users/johnny
导航到/users/jolyne
时,相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会被调用。
404路由
const routes = [
// 将匹配所有内容并将其放在$route.params.pathMatch下
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
]
高级匹配模式
一般使用静态路由/abc
和动态路由/:abc
就够我们用了,但vue-router
还为我们提供了正则匹配模式。
自定义正则
const routes = [
// /:orderId -> 仅匹配数字
{ path: '/:orderId(\\d+)' },
// /:productName -> 匹配其他任何内容
{ path: '/:productName' },
]
可重复参数
如果要匹配多级路由比如/abc/1234/xyz
可以使用*
(将参数重复0次或多次)和+
(将参数重复1次或多次)。
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
// 仅匹配数字
// 匹配 /1, /1/2, 等
{ path: '/:chapters(\\d+)+' },
// 匹配 /, /1, /1/2, 等
{ path: '/:chapters(\\d+)*' },
]
此时如果使用
$router.push
进行跳转需要传入数组类型参数方可。$router.push({name: 'home', params: {chapters: ['one', 'two']}})
可选参数
如果要想让一个参数可传可不传则可以使用?
(0次或1次)。
const routes = [
// 匹配 /users 和 /users/posva
{ path: '/users/:userId?' },
// 匹配 /users 和 /users/42
{ path: '/users/:userId(\\d+)?' },
]
*
号虽然也可以做到,但它与?
的区别是?
参数只能用0或者1次,而*
则还可以让参数用多次。
嵌套路由
有些项目中会存在一些嵌套组件,比如用户页面是由个人信息、设置、收藏等组件组成的,而这些组件假设也有对应的路由,并且点击后是在用户这个组件中展示(类似于手风琴式的UI),这种路由就属于嵌套路由。
对于嵌套路由vue-router
提供了children
属性来配置:
const routes = [
{
path: '/user/:id',
component: User,
children: [
// 当 /user/:id 匹配成功
// UserHome 将被渲染到 User 的 <router-view> 内部
{ path: '', component: UserHome },
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 <router-view> 内部
path: 'posts',
component: UserPosts,
},
],
},
]
<!-- User Component -->
<template>
...
<router-view/>
</template>
命名视图
如果想同时展示多个视图,并且不是嵌套展示,这时候就需要命名视图了。
router-view
默认的name
属性值为default
,多个视图就可以用多个router-view
并设置上name
属性(必须否则将会展示同样的内容)。
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
const routes = [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
],
嵌套命名视图同理,在需要嵌套的组件中使用router-view
组件,并在嵌套路由中使用命名视图即可。
重定向和别名
重定向
在使用redirect
时可以不用写component
,因为它是直接通过重定向后的配置访问的组件,这里可以不配置,除非是嵌套路由。
// path的方式
const router = [{path: "/abc", redirect: "/xyz"}]
// name的方式
const router = [{path: "/abc", redirect: {name: "demo"}}]
//方法的形式
const router = [{
path: "/abc/:s", redirect: to => {
// to和$route是一样的对象类型
return {path: "/s", query: {q: to.params.s}}
}
}]
别名
重定向是指当用户访问/home
时,URL 会被/
替换,然后匹配成/
。那么什么是别名呢?
将/
别名为/home
,意味着当用户访问/home
时,URL 仍然是/home
,但会被匹配为用户正在访问/
。
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
嵌套路由中使用别名可以使用绝对路径来改变这个路由的路径
const routes = [
{
path: '/users',
component: UsersLayout,
children: [
// 为这 3 个 URL 呈现 UserList
// - /users
// - /users/list
// - /people
{ path: '', component: UserList, alias: ['/people', 'list'] },
],
},
]
如果路由中有参数,那么绝对路径的别名路由必须包含此参数
const routes = [
{
path: '/users/:id',
component: UsersByIdLayout,
children: [
// 为这 3 个 URL 呈现 UserDetails
// - /users/24
// - /users/24/profile
// - /24
{ path: 'profile', component: UserDetails, alias: ['/:id', ''] },
],
},
]
路由组件传参
vue-router
提供了另一种传参给组件的方法,那就是props
属性。props
拥有布尔模式、对象模式、函数模式。
- 布尔模式
props
为true
时$router.params
将被设置为组件的props
const routes = [{ path: '/user/:id', component: User, props: true }]
// 命名视图必须为为每个视图定义props的值
const routes = [
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
- 对象模式
props
是对象时,内容会原样设置到组件的props
中。
const routes = [
{
path: '/user/profile',
component: User,
props: { isLogin: true }
}
]
- 函数模式
也可以是一个函数,参数为$route
类型,返回值将被设置到组件的props
中。
const routes = [
{
path: '/user',
component: User,
props: route => ({ name: route.query.abc })
}
]
/user?abc=lisi
这个URL将传递{name: 'lisi'}
给User
组件。
历史模式
创建路由时demo使用的历史模式是HTML5
模式,除此还有Hash
模式。
HTML5
模式
这种模式对SEO
很友好,URL看上去与正常的URL没什么区别。
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [],
})
但如果用户直接访问http://demo.com/user/123
会是404错误,这需要在服务器配置一个会退路由来解决,让其为匹配到URL时找到你程序的index.html
。
以下仅提供Nginx
的配置,其他服务器请看官方文档。
location / {
try_files $uri $uri/ /index.html;
}
Hash
模式
Hash
模式会在URL中用哈希字符#
隔开实际上用到的URL,这种模式与常规URL不同,因此对SEO
不是很友好。
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [],
})
进阶内容
导航守卫
全局前置守卫
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
to
目标路由对象from
来源路由对象- 可以返回
false
来取消导航,也可以返回一个路由地址(可以是一个字符串或路由对象,返回的内容跟router.push()
中可以用的参数是一样的)
除此支持旧版的第三个参数next
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) {
next({name: 'Login'})
} else {
next()
}
})
全局解析守卫
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
全局后置钩子
router.afterEach((to, from) => {
console.log(to.fullPath)
})
// 没有next参数,只有failure,在此钩子无法终止导航
router.afterEach((to, from, failure) => {
if(failure){
console.log(to.fullPath);
}
})
这三个守卫/钩子跟拦截器的效果差不多,顺序就是按命名来理解。
路由级导航守卫
此导航守卫只在进入路由时才会触发,不会在params
、query
、hash
变更时触发。
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// 返回值与全局前置导航守卫一样
return true;
},
},
]
组件内导航守卫
提供三个可用的API:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
export default {
name: 'Demo',
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例`this`,因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 比如用的是一个带有动态参数的路径 `/users/:id`,参数变化组件会复用
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
在beforeRouteEnter
中虽然无法直接使用this
访问实例,但是可以用next
参数,在导航确认后调用此回调函数访问实例。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
完整的导航流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫(2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发
DOM
更新。 - 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
数据元信息
const routes = [
{
path: '/',
name: 'Default',
component: () => import('@/views/home/Home'),
meta: {
title: '首页',
needAuth: false
}
}
];
router.beforeEach((to, from) => {
// 设置每个路由页面的标题
document.title = to.meta.title;
return true;
})