这是 Vue 全家桶组件系列的文章,梳理一下比较难懂的几个点,此篇文章要说的就是 Vue-Router
什么是路由
在 web 领域,我们可以将 路由 简单的理解为 控制器, 这个控制器主要做什么呢?它能将输入到浏览器中的 url 导向至对应页面的组件,然后浏览器就将这个组件渲染出来。这个过程就是 路由 。
什么是 Vue-Router
以下是官方说明:
Vue Router 是 Vue.js 官方的 路由管理器
其实跟 Vuex 的思想是很像的,它通过一个全局的router
,来对浏览器中的 url 进行管理,最后导向不同的页面。
核心概念
主要有几大核心概念
- 动态路由匹配
- 重定向和别名
- 路由组件传参
- History 模式
- 导航守卫
- 路由懒加载
动态路由匹配
什么是动态路由匹配?比如我输入的 url 中有 /user/foo
和 /user/bar
,我们希望在输入这两个 url 后导向的是同一个页面的同一个组件,那么我们可以这么写(类似于用个变量代替 /user/
后面的参数,然后就往这个变量丢参数就完事儿了,因为不写死,所以就是动态的)// User 组件
const User = {
template: '<div>User</div>'
}
// 路由
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
同时在 path
中也可以设置多段 路径的参数,比如我想要输入 /user/foo/post/100
,你可以这样写/user/:id/post/:post_id
当然了,动态路径的参数名字是可以随便取的,我们可以在 $route.params
中看到这个console.log($route.params); // {id: 'foo', post_id: '100'}
注意一下这里的匹配优先级,若存在多个路径匹配同一个路由的情况,如const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/xxx', component: User1 },
{ path: '/xxx', component: User2 }
]
})
那么会优先匹配先定义的,也就是输入 /xxx
,会匹配到 User1
重定向和别名
Vue-Router 的这个功能可以实现一个逻辑,这个逻辑是什么呢?拿知乎来举例,若我是未登录的状态,那么当访问个人信息页面时,应该重定向到登录页面,如下const router = new VueRouter({
routes: [
{ path: '/login', component: Login }
{ path: '/userInfo', redirect: '/login' }
]
})
也可以是一个已经命名过的路由const router = new VueRouter({
routes: [
{ path: '/login', name: 'login', component: Login },
{ path: '/userInfo', redirect: { name: 'login' }}
]
})
也可以是一个方法const router = new VueRouter({
routes: [
{ path: '/userInfo', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
return {
path: '/login',
name: 'login',
component: Login
}
}}
]
})
同样,你可以用 别名 的方式,什么是别名?比如在浏览器中输入的是 /bar
,但实际上访问的是 /foo
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo, alias: '/bar' }
]
})
路由组件传参
什么是路由组件传参?我们可以这么理解,其实就是在浏览器输入一串 url ,然后渲染的对应组件能够接受到这串 url 中的某些参数,这就是 参数
路由 ------> 组件
路由组件传参有 3 种方式: 布尔模式、对象模式、函数模式
- 布尔模式
其实就是通过 props
传参,比如我输入 /user/foo
这个 url,我想把 url 中 foo
这个参数传给组件,可以这么写// User 组件
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
// 路由
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true }
]
})
前提条件是要设置对应匹配路由的 props
为 true
如果你在使用 router-view
时使用到了 name
这个属性,比如// 渲染 user
<router-view></router-view>
// 渲染 sidebar
<router-view name="sidebar"></router-view>
你就需要为每个对应的 命名视图
添加 props
const router = new VueRouter({
routes: [
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
- 对象模式
比如组件中 props
是个对象// User 组件
const User = {
props: {
name: ''
},
template: '<div>User {{ name }}</div>'
}
// 路由
const router = new VueRouter({
routes: [
{ path: '/user/new-name', component: User, props: { name: 'strugglebak' } }
]
})
- 函数模式
其实本质上这个函数返回的还是一个对象,比如在浏览器中输入 /search?name=strugglebak
表示查询,可以这么写// User 组件
const User = {
props: {
name: ''
},
template: '<div>User {{ name }}</div>'
}
// 路由
const router = new VueRouter({
routes: [
{ path: '/search', component: User, props: (route) => ({ query: route.query.name }) }
]
})
router.query.name
返回的就是 strugglebak
这个字符串
History 模式
什么是 History 模式?我们知道,网易云的 url 一般来说是这种形式http://music.163.com/#/my/m/music/playlist?id=121597667
在播放歌曲时,每个 url 下都有一个 #
符号,我们知道这个符号是 url 的 hash 部分,这个符号后面那一大串不会传递到服务端,那么它有什么用?它的作用就是 通过这个 hash 来模拟传统的后端路由器,判断哪些请求可以直接导向哪个组件,进而渲染出这个组件,这就是前端路由器的作用。但是带个 #
号的 url 看起来比较丑,你看哪些传统的 web 应用的 url 就没有,那么有没有一种办法来去掉这个 #
号呢?
有的,Vue-Router 提供了这个 History模式,你只需要做如下设置const router = new VueRouter({
mode: 'history',
routes: [...]
})
url 就会和正常的 url 一样没有 #
号,就对 url 进行了美化处理
但是这个还需要后端配置,官方文档是这么说的
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。
所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
导航守卫
什么是导航守卫呢?这其实就相当于一个 拦截器,在路由发生变化的之中通过这层拦截来实现一些业务需求。一般来说,我们认为当你点击 a 链接进行 重定向式跳转 时路由就产生 变化 了。
但是这里需要注意,params 或者 query 的变化并不会触发 enter/leave 这类路由拦截器,你可以通过const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
或者const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// 对路由变化作出响应...
}
}
这样的方式来监听以上的变化
导航守卫有几个 api,分别是
- 全局的守卫
beforeEach
,beforeResolve
,afterEach
- 路由独享守卫
beforeEnter
- 组件内的守卫
beforeRouterEnter
,beforeRouterUpdate
,beforeRouterLeave
首先我们应该先看看一个完整的导航解析过程是怎样的 (失活组件)beforeRouterLeave
1. 导航触发 ---------------------------> beforeEach
(复用组件)beforeRouterUpdate
2. ---------------------------> beforeEnter
3. ---------------------------> 解析组件
(激活组件)beforeEnter
4. ---------------------------> beforeResolve
5. 导航确认 ---------------------------> afterEach
6. 触发 DOM 更新
基本上解析过程就代表了它们之间的区别了
注意 beforeEnter
是 可以 在路由配置上定义const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
以及组件内的路由用法如下// Foo 组件
const foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
路由懒加载
路由的懒加载是什么呢?一般我们使用 webpack 进行打包时,js 代码会变得很大,这样会对页面加载的性能造成影响,如何提升性能呢?就是 按需加载,当路由被访问的时候才加载对应组件。具体要怎么做呢?就是分离组件代码。
这里需要分两步来做
将异步组件定义为返回一个 Promise 的工厂函数
const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
在 webpack 中 import
import('./Foo.vue').then((module)=> {
//...
})
还有其他常用 API
常用 API 有 router-link
/router-view
/this.$router.push
/this.$router.replace
/this.$route.params
router-link
一般写在需要进行路由跳转的地方,它最终渲染的是一个 a 标签<!-- 使用字符串 -->
<router-link to="xxx">xxx</router-link>
<!-- 使用 v-bind 绑定变量 -->
<router-link :to="xxx">xxx</router-link>
<!-- 绑定对象 -->
<router-link :to="{
path: 'xxx'
}">xxx</router-link>
<!-- 绑定命名的路由 -->
<router-link :to="{
name: 'user',
params: { userId: 123 }
}">User</router-link>
<!-- 带查询参数,下面的结果为 /register?plan=private -->
<router-link :to="{
path: 'register',
query: { plan: 'private' }
}">Register</router-link>
router-view
如果 router-view
设置了名称,则会渲染对应的路由配置中 components
下的相应组件,一般我们是这么写<!-- 渲染一个 name 为 main 的组件 -->
<router-view name="main"></router-view>
<!-- 渲染一个 name 为 slidebar 的组件 -->
<router-view name="slidebar"></router-view>
然后在路由中有如下配置const router = new VueRouter({
routes: [
{
name:'post',
path:'/post',
components:{
main: Article,
slidebar: SlideBar
}
}
]
})
this.$router.push
一个 router-link
的替代品,作用就是 向 history 添加记录,可以有如下用法// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
注意,若写了 path
,则 params
会被忽略掉(query
除外),所以你应该这么写const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
this.$router.replace
跟上面的 push
方法很像,不过作用是 替换掉当前的 history 记录,用法跟上面的相同
this.$router.params
就是一个 key/value
对象,之前在 动态路由匹配 中说过,它里面保存的就是路由参数
总结
Vue-Router 是前端 SPA 项目的利器,它可以提升用户的体验,增加开发效率。本文只是简单梳理了一下 Vue-Router 开发经常会用到的东西,其他小细节还得多去翻翻 官方文档