通常我们会用一个或多个路由表,来匹配所有页面的路径。但这不能满足一些特定的需求。
# 解惑
好几次被问到怎么区分$route
和 $router
,这里统一给出解释:
- this.$router:指的是
router
实例,也就是new VueRouter()
的执行结果,使用效果与router
相同。有这种写法是因为Vue并不想在每个独立需要封装路由的组件中都导入路由,因此在全局创建了该对象。 - this.$route:指的是当前 激活 的 路由信息对象,又称路由记录,该对象是只读的,内部属性不可改变,当路由发生重定向或路由参数改变时,该对象会被重新计算。一般我们会通过
watch
来监听它们。
# 基本路由
对于基本路由,我们需要精准的匹配路由内容,才能实现页面跳转。其编写风格大致如下
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import 404 from '@/404'
Vue.use(Router)
const router = new Router({
routes: [
{ path: '/', redirect: '/index' },
{ path: '/index', component: HelloWorld, alias: '/home' },
{ path: '/404', meta: { title: '错误页面' }, component: 404 },
{ path: '/b', redirect: to => {
// 接收目标路由to作为参数,return值作为动态响应的路径
} }
]
})
export default router
在这个案例中,this.$router
就是router
常量,而this.$route
是在实例化后,即将登场的那个路由对象,如假设为{ path: '/index',component: HelloWorld }
。
meta
为路由元信息,常提供给 导航守卫 和this.$route
使用
alias
是路由别名,其作用是当匹配到/home
时,访问的是/home
路由但真正走的是/index
路由的页面
import为何能实现路由懒加载
曾经我也困惑过作为编译时调用的import
是如何实现懒加载的。其实对比
import('./index.vue')
(编译时加载)和
const Index = () => import('./index.vue')
(懒加载)
简单理解:后者编译时只是声明了函数,只有使用时才运行该内容。
深入一些:利用Promise 可以将异步组件定义为返回一个Promise 的工厂函数,又因为webpack2中动态import
能实现代码分块,从而达到定义一个能被Webpack自动代码分割的异步组件的效果
# 动态路由匹配
例如,我们有一个 user
组件,对于所有ID各不相同的用户,都要使用这个组件来渲染,因为用户ID未知,不可能再路由表里书写这个路由,这时,就需要使用到 vue-router
中的 “动态路径参数” 来达到这个效果
const router = new VueRouter({
routes: [
// 动态路径参数,以冒号开头
{ path: '/user/:id', component: () => import('@/components/User') }
]
})
现在呢,对于如/user/12345
和 /user/e9e0jh830d
类型的路由,都会被映射到相同的路由路径上,而 /user/
后的则是本路径的参数,页面初始化时并不关心它是什么内容,认为这是提供给页面内部使用的一个参数
既然是参数,页面就应该有能力获取,通常我们需要根据这个参数发起请求获取页面更详细的渲染数据,获取参数的办法如下
- 单个参数
Path:{ path: '/user/:id' }
Url:/user/e9e0jh830d
const para = this.$route.params
// { id: e9e0jh830d}
- 支持多个参数
Path:{ path: 'user/:username/post/:post_id' }
Url:/user/zhangfs/post/330473
const para = this.$route.params
// { username: zhangfs, post_id: '330473' }
# 响应路由参数的变化!!!
有个很经典的问题:假设在一个 User 列表中,用户切换查看不同账户信息,Url上/user/:id
后面的 id
发生了变化实现了用户切换,然而页面本该展示不同用户信息的数据并没有更新。
原因是Vue在设计初衷,为了最大化高效利用组件,并不会销毁后再创建,也就是说,组件的生命钩子如Created
将不会再次被触发调用,这就要求我们自己实现触发。笔者提供4种思路
(1)监听路由参数,发生变化则重新获取页面渲染数据
(2)监听路由(Vue推荐)
watch: {
'$route' (to, from) { ... } // to-from 都能监听出变化做出响应
}
(3)使用组件内导航守卫 beforeRouteUpdate
beforeRouteUpdate (to, from, next) { ... } // 别忘记调用next()
(4)为
增加唯一的key
。这种方式是逆Vue思路来的,设计Key
关键字实现销毁组件重新挂载来保证生命钩子重新触发
computed: {
key() {
return this.$route.name ? this.$route.name + new Date() : this.$route + new Date()
}
}
# 嵌套的子路由
通常,在app中声明的路由为 顶级路由,作为顶层的出口,渲染最高级路由匹配到的组件。
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
如果需要实现子路由,如匹配 /user/:id/posts
,则需要设置嵌套的路由,使用children
属性定义
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 中
path: 'posts',
component: UserPosts
}
]
}
]
})
【注意】以/
开头的路径会被当做根路径。也就是说允许你充分的使用嵌套组件而无需设置嵌套的路径
该例中,当遇到访问/user
的路径不会渲染任何东西,是因为没有匹配到任何合适的子路由。当然你可以提供一个空的子路由来承接该路径
children: [
{ path: '', component: UserHome }
]
# 编程式导航
我们知道
实际上是创建了a
标签来实现导航链接,我们还可以借助 router
的实例方法通过编写代码来实现。注意,如在开篇描述,组件内部应该使用this.$router
访问
# this.$router.push()
声明式 | 编程式 |
---|---|
|
this.$router.push(...) |
使用语法: this.$router.push(location, onComplete回调. onAbort回调)
location
接受一个字符串路径,或一个描述地址的对象
this.$router.push('dashboard') // -> /dashboard
this.$router.push({ path: 'dashboard' }) // -> /dashboard
this.$router.push({ name: 'user', parmas: { userId: '123' } }) // -> /user/123
this.$router.push({ path: 'register', query: { plan: 'private' } }) // -> /register?plan=private
【注】注意观察第三个和第四个,重定向带路径参数的路由时,不能指定 path
,因为提供了 path
后 params
参数会自动被忽略。如果想要用path
则需要修改如下:
const userId = '123'
this.$router.push({ path: `/user/${userId}` }) // -> /user/123
// 错误示例
this.$router.push({ path: `/user`, params: { userId: '123' } }) // -> /user
同样的规则适用于
的 to
属性
# this.$router.replace()
声明式 | 编程式 |
---|---|
|
this.$router.replace(...) |
跟 router.push
很像,唯一的不同就是,它不会向 history
添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history
记录。
# this.$router.go(n)
这个方法的参数是一个整数,意思是在 history
记录中向前或者后退多少步,类似 window.history.go(n)
。
this.$router.go(1) // 前进一步
this.$router.go(-1) // 后退一步
this.$router.go(-100) // 如果记录不够用,默默失败不阻塞
# 常用路由对象属性
- 当前路由 -
$route.path
Path:{ path: '/user/:id' }
Url:user/12345
说明:总是解析为绝对路径,无视是否为路径参数。返回对象
const path = this.$route.path
// user/12345
- url参数 -
$route.query
Path:{ path: '/user' }
Url:/user?id=e9e0jh830d
const query = this.$route.query
// { id: e9e0jh830d }
- 路由参数-
$route.params
Path:{ path: '/user/:id' }
Url:/user/12345
const para = this.$route.params
// { id: 12345 }
# 常用路由导航守卫
- 全局前置守卫 -
router.beforeEach
这是最常使用也是最重要的守卫之一,使用时要调用 next()
方法表示导航 resolved
;因为守卫是异步解析执行,没有resolve
的导航守卫处在等待中阻断执行进程,路由也不会实现跳转
next()
允许接收一个参数:(1)false
- 中断当前导航,(2){ path: '/route_name' }
- 实现跳转
router.beforeEach((to, from, next) => {
// `to` 和 `from`都是路由对象
})
- 全局后置钩子 -
router.afterEach
与前置导航守卫不同的是,afterEach
并不是“守卫”,而是“钩子”,它不会阻塞线程,也不需要调用next()
来表达resolve
状态。可以认为是线程流水线里的一个作业而已
router.afterEach((to, from) => { ... })
- 私有路由守卫 -
router.beforeEnter
与beforeEach
和afterEach
不同的是,beforeEnter
是针对单个路由进行配置的导航守卫,作用于所在路由内而不影响其他路由路径。该守卫也需要调用next()
来表示守卫结束状态
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
另外还有 组件内的守卫 支持在组件内直接定义导航守卫。需注意的是,其中的beforeRouterEnter
守卫不能访问this
对象,理由是守卫在导航确认前即将登场的新组件并未被创建,需要通过 next(vm)
中的 vm
参数来调用组件实例
export default {
data () {
...
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
methods: { ... }
}
- 完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数
# 路由过渡动效
是基本的动态组件,可以用
组件增加过渡效果
以上方式会给所有的路由增加相同的简单的切换过渡效果,如果想个性化给每个路由增加,则需要在组件内使用
并设置不同的 name
实现页面级的过渡效果
.fade-enter {
opacity:0;
}
.fade-leave{
opacity:1;
}
.fade-enter-active{
transition:opacity .5s;
}
.fade-leave-active{
opacity:0;
transition:opacity .5s;
}
# 路由切换滚动
当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router
能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。该功能需要浏览器支持history.pushBehavior
API
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition // 滚动到上一个位置
} else {
return { x: 0, y: 0 } // 滚动到顶部
}
}
})
或者可以模拟滚动到某个锚点
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash
}
}
}
甚至可以配合路由元信息meta
实现更细颗粒度控制滚动,和利用返回一个Promise来得出预期的位置滚动。