路由的配置
Vue 的特点是数据驱动和组件化,根据组件化的特点,我们可以很方便的用 Vue 来实现单页面应用程序。在 Vue.js 中,我们已经可以通过组件名来切换组件,但是这样做浏览器的地址栏内容不会发生变化,为了解决这一问题,我们可以引入 Vue Router 通过路由来标识组件,并且把路由带入到地址栏中。这里我们通常使用 hash (#)
形式来约定路由,因为 HTTP 请求中不会包含 hash (#)
相关的内容,所以单页面程序中的页面跳转主要用 hash (#)
实现。
绑定路由
基础绑定
首先我们要进行路由与组件的绑定,绑定之后要把整个路由实例挂载到全局 Vue 实例上,这里我们举一个简单的例子:
// 1. 定义组件
// 在项目开发中,通常会使用 import 的方式引入
const componentA = { template: '<div> 组件A </div>' }
const componentB = { template: '<div> 组件B </div>' }
// 2. 定义路由,数组格式
const routes = [
{ path: '/componentA', component: componentA },
{ path: '/componentB', component: componentB }
]
// 3. 创建路由实例
let router = new VueRouter({
routes: routes
})
// 4. 将路由实例挂载到全局 Vue 实例中
new Vue({
router,
render: h => h(App)
}).$mount('#app')
动态路由匹配
router 路由传参有两种方式,一种是将参数以 ?key=value&key=value
的形式拼接到路由后面,另一种是将参数拼合到路由中,例如:/user/123
,其中 123
是参数,这种传参方式需要在路由配置的时候声明参数的 key
值,作为路径参数:
const routes = [
{ path: '/componentA/:id', component: componentA },
{ path: '/componentB/:id', component: componentB }
]
一个路径参数使用冒号 :
标记。当匹配到一个路由时,参数值会被设置到 this.$route.params
,可以在每个组件内使用。$route
用来存放路由中的相关信息。
注意,当使用路由参数时,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。如果有需要,可以利用 catch
监听。
命名路由
有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。可以在创建实例的时候,在 routes
配置中给某个路由设置名称。
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
嵌套路由
router-view
是路由出口,路由匹配到的组件将渲染在这里。同样地,一个被渲染组件同样可以包含自己的嵌套 router-view
。要在嵌套的出口中渲染组件,需要在父级路由的参数中使用 children
配置:
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
}
]
}
]
})
重定向
重定向也是通过 routes
配置来完成:
// 从 /a 重定向到 /b
const router1 = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
// 重定向到命名路由
const router2 = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
经过一顿操作,现在我们已经可以在项目中使用路由了。
路由的基本使用
在项目中,我们经常会遇到两种情况来使用路由进行跳转页面。第一种是操作页面元素后,子页面进行跳转;第二种是在 JS 中操作页面的跳转。这两种需求引出了路由的两种使用方式:声明式导航 和 编程式导航。
声明式导航其实是 Vue 已经把编程式导航封装成了一个组件来给我们使用:router-link
,将要跳转的路由当做 to
属性的值传入,所以说,点击 <router-link :to=" xxx "></router-link>
等同于编程式导航的 $router.push()
编程式导航:在 Vue 实例中,可以通过 $router
访问路由实例,其中提供了几个方法方便我们操作路由:
push
:是我们最常用的方法,可以接收一个字符串或者设置对象作为参数
// 字符串
this.$router.push('home')
// 对象
this.$router.push({ path: 'home' })
// 命名的路由
this.$router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' }})
注意:path
和 params
同时存在时,params
失效,取而代之的是将 params
直接拼入 path
中
this.$router.push({ path: '/register', params: { userId: '123' }}) // params 失效
// 应使用
const userId = '123'
this.$router.push({ path: '/register/${userId}' }) // --> /register/123
go
:$router.go()
等同于history.go()
// 在浏览器记录中前进一步,等同于 history.forward()
this.$router.go(1)
// 后退一步记录,等同于 history.back()
this.$router.go(-1)
// 前进 3 步记录
this.$router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
this.$router.go(-100)
this.$router.go(100)
路由守卫
路由守卫是路由在切换过程中一系列钩子函数的统称,每个守卫方法接收三个参数:
-
to
:Route 实例,即将要进入的目标路由对象 -
from
:Route 实例,即将要离开的目标路由对象 -
next
:Function ,一定要调用该方法来resolve
这个钩子。执行效果依赖next
方法的调用参数。a.
next()
:进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。b.
next(false)
:中断跳转c.
next('/')
:跳转到指定路由
全局路由守卫
router.beforeEach
:全局前置守卫
router.beforeResolve
:全局解析守卫,和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
router.afterEach
:全局后置钩子,不会接收 next
函数
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
next()
})
router.beforeResolve((to, from, next) => {
// ...
next()
})
router.afterEach((to, from) => {
// ...
})
组件内的守卫
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`
}
}
路由独享的守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。