写在前面:
1.本文是个人课上学习内容的总结和梳理,主要知识点来自于“开课吧”线上直播课程,以及Vue官方文档。
2.目前尚处于Vue乃至前端入门阶段,因此很多内容理解的不是很透彻,文章更多是用来学习记录而非干货分享。
因此,建议如果需要解决项目问题,还是去看一下其他大佬的文档已经vue官方文档(一手资料)
vue router
简介
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于 Vue.js 过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的 CSS class 的链接
- HTML5 历史模式或 hash 模式,在 IE9 中自动降级
- 自定义的滚动条行为
本质
通过Vue Router建立起页面(视图)与URL之间的映射关系。
对象
- router:路由实例对象
- route:路由对象
- 一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,还有 URL 匹配到的路由记录 (route records)。
可以理解为,router是一组容器管理了一堆route,route管理的是当前URL和组件的映射关系。
安装
-
通过vue cli提供的插件安装方式
vue add @vue/router -
npm手动安装
vue i vue-router
路由
动态路由
上面提到了Vue Router的本质就是建立URL和组件的映射关系,往往实际应用场景中会出现需要匹配多个url的路由来映射到同一个组件上。比如需要每一个用户登陆后,携带用户id的url都应该映射到同一个用户组件上,这时就需要用到动态路由进行匹配:
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
这样使用冒号:标记的内容就是动态匹配的,后面的id参数值会自动写入到$route.params.id之中,可以全局访问到。
以上是动态路由的基本使用场景,但是还需要注意几个问题:
-
匹配优先级。
- 当我们以/user/开头写了好几个匹配规则之后,如果一个URL同时满足这些规则,那么哪个规则先定义就执行哪个规则。
-
复用性问题。
- 当复用路由参数时,例如从
/user/foo导航到/user/bar,原来的组件实例会被复用。因为两个路由都渲染到同一个组件,比起销毁再创建,复用则显得更加高效。 - 我们可以侦听
watch $route的变化或者使用导航守卫beforeRouteUpdate,来解决这个问题。
const User = { template: '...', watch: { $route(to, from) { // 对路由变化作出响应... } } }const User = { template: '...', beforeRouteUpdate (to, from, next) { // 对路由变化作出响应... // 一定要写上next(),否则进程会卡在这里 } } - 当复用路由参数时,例如从
路由组件传参
我们之前解释过了,route是表示单一组件和url之间的映射关系,也就是$route只能在有限的url內使用,耦合性高,为了消除耦合性,我们可以使用props参数(老朋友了)进行传参。
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加props选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
嵌套路由
根据实际应用场景,有时候需要将URL各段动态地址按照某种结构对应到视图的不同组件上,如下:
/user/foo/profile /user/foo/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
利用嵌套路由我们可以比较轻松的实现这种业务逻辑,但是不推荐嵌套层数超过两层,否则代码可读性太差不容易维护。
我们知道是我们路由的渲染出口,所有内容都会渲染到该标签內,那我们只要在上面的/user/组件的模版里面再加一层,并且使用route提供的children参数不是就轻松实现了嵌套路由么,实现代码如下:
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
可以看到children参数內是嵌套组件的参数,所有options api都是照搬user组件的写法,所以理论上UserProfile、UserPosts组件还可以继续嵌套其他组件。不过还是那句话,为了代码的可维护性,2层嵌套就够了。
还需要注意的一点是,我们在children.path的配置时,是没有写/的,这样URL也是会自动补上/并拼在一起。不要自己补上/否则会被识别成根目录。
命名视图
有时候我们需要在同一个页面内有多个同级的渲染出口,这时候就需要给每一个起一个名字,告诉每一个出口他自己应该对应哪个一个视图组件。
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
在配置router的时候,需要给对应的组件命名清楚:(默认不起名字就是default)
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
命名路由
就像我们给每一个视图组件起了个名字一样,往往我们也是需要给路由起个名字的,这样更方便我们调用。这种方式在后面会写到的导航当中,是十分常用的。
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
这样我们我们就给这个路由起好了名字啦,他就叫user,这样我们在导航中就可以方便的声明我们是要跳转到哪个路由上面。
重定向
我们可以利用重定向将/自动跳转到/home,但是要注意的一点是,所有导航守卫都是只针对跳转后的目标生效的,因此在/内定义的任何导航守卫都不会生效。
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' }
]
})
也可以重定向到一个命名路由上,只要告诉他路由的名字即可。
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'home' }}
]
})
别名
这个类似于重定向:
- 重定向是从
/跳转到了/home,路由匹配到的也是/home的路由组件 - 别名是
/和/jia根本就是一个东西的两个叫法而已,路由匹配到的还是/的路由组件
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
导航
上面说了这么多,都是用过操作URL来实现渲染不同的视图,不过我们怎么可能让用户用这种操作URL的方式呢,为此,vue router提供了一种名为导航的方式来给用户使用,分别是声明式和编程式。
声明式导航
<router-link> 组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。
通过to参数来声明连接指向的组件
<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>
<!-- 渲染结果 -->
<a href="home">Home</a>
编程式导航
除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。主要有三种方法:
-
this.$router.push()
-
上述声明式导航其实就是在底层调用了
push方法,所以语法类似,可以传入路由名称、对象、以及一些参数。// 字符串 router.push('home') // 对象 router.push({ path: 'home' }) // 命名的路由 router.push({ name: 'user', params: { userId: '123' }}) -
push方法是玩history栈里面塞进去了一个新数据,不影响其他已经入栈的数据,所以在浏览器上是可以回退到上一级视图的。
-
-
this.$router.replace()
replace方法和push方法的语法一致,这里不进行赘述了。- 但是他对数据栈的操作是不一样的。
replace方法是在数据栈中将当前路由替换为目标路由,调用完该方法之后,数据栈之中就没有了原来的路由组件,因此在浏览器上不可以回退到上一级视图。
-
this.$router.go()
-
go方法的语法就不太一样了,他是通过传入“步数”来控制路由的跳转的。这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似window.history.go(n)。// 在浏览器记录中前进一步,等同于 history.forward() router.go(1) // 后退一步记录,等同于 history.back() router.go(-1) // 前进 3 步记录 router.go(3) // 如果 history 记录不够用,那就默默地失败呗 router.go(-100) router.go(100)
-
导航守卫
上面说完了导航的两种调用方式,那么可以看出,导航其实就是表示路由正在变化,从一个跳转到了另一个。
导航守卫就为我们提供了一些接口,让我在全局或者组件内部监控处于不同阶段的变化,并拦截特定的时间点在该点作出我们需要的逻辑动作:
- 全局守卫
- router.beforeEach
- router.beforeResolve
- router.afterEach
- 路由独享守卫
- beforeEnter
- 组件守卫
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
通过上述的API,我们可以在我们想要的导航阶段进行我们想要的逻辑处理,但是一定不要忘记在自己代码中中加上next()让导航继续执行下去,否则将会卡住不动。
关于导航守卫我准备单独写一篇文章,这里就不展开了,预留一个导航守卫的文章链接
模式
hash
使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
history
我们可以在路由那设置成的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。(暂时还没时间看这个API的具体内容)
const router = new VueRouter({
mode: 'history',
routes: [...]
})
但是这种模式有一个弊端,就是需要后端良好的配合,否则当范围到某些在VueRouter上没有定义的URL的时候,就会抛出报错。