一. 路由的简单使用
<div id='app'>
<p>
<!-- 使用 router-link 组件来导航,通过传入 to 属性指定链接 -->
<!-- <router-link> 默认会被渲染成一个 a 标签 -->
<router-link to='/user/foo'>Go to Foo</router-link>
<router-link to='/user/bar'>Go to Bar</router-link>
</p>
<!-- 路由出口,匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
const User = {
template: `
<div>
<h1>User {{ $route.params.id }}</h1>
</div>
`
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
new Vue({
router
}).$mount('#app')
二. 动态路由匹配
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,有一个user组件,对于所有ID各不相同的用户,都要使用这个组件来渲染,那么可以在 vue-router 的路由路径中使用动态路径参数
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
{path: '/user/:id', component: User}
]
})
现在,像 user/foo 和 user/bar 都将映射到相同的路由。一个路径参数使用冒号(:)标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用
可以在一个路由中设置多段路径参数,对应的值都会设置到 $route.params 中
模式 | 匹配路径 | $route.params |
---|---|---|
/user/:username | /user/evan | { username: "evan" } |
/user/:username/post/:post_id | /user/evan/post/123 | { username: "evan", post_id: 123 } |
当使用路由参数时,原来组件实例会被复用,意味着组件的生命周期钩子不会再被调用,如果想对路由参数的变化作出响应,可以简单的 watch $route 对象,或者使用beforeRouterUpdate
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化做出响应
}
},
beforeRouteUpdate (to, from, next) {
}
}
三. 嵌套路由
一个被渲染组件同样可以包含自己的嵌套 <router-view>,例如,在User组件的模板中添加一个 <router-view>
const User = {
template: `
<div>
<h1>User {{ $route.params.id }}</h1>
<router-view></router-view>
</div>
`
}
要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: user,
children: [
{
// 当 /user/:id/profile 匹配成功,UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
path: 'posts',
component: UserPosts
}
]
]
})
四.编程式的导航
除了使用<router-link> 创建 a 标签来定义导航链接,还可以借助 router 实例方法,通过编写代码来实现。想要导航到不同的URL,则使用router.push 方法。这个方法会向 history 栈添加一个新的记录,所以当用户点击浏览器后退按钮时,则回到之前的URL。
当点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to='...'> 等同于调用router.push(...)
<!-- onComplete 和 onAbort 将会在导航成功完成(在所有的异步钩子被解析之后)或终止(导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由)的时候进行相应的调用 -->
router.push(location, onComplete?, onAbort?)
<!-- 跟router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是替换掉当前的history记录 -->
router.replace(location, onComplete?, onAbort?)
该方法的参数可以是一个字符串路径,或者一个描述地址的对象,例如:
// 字符串
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 会被忽略,同样的规则也适用于 router-link 组件的 to 属性
const userId = 123
router.push({ name: 'user', params: {userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 无效
router.push({ path: '/user', params: { userId }}) // -> /user
如果目的地和当前路由相同,只有参数发生了变化(比如从一个用户资料到另一个 /users/1 -> /users/2),需要使用 beforeRouteUpdate 来响应这个变化(比如抓取用户信息)
router.go(n),这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进3步记录
router.go(3)
// 如果 history 记录不够用则会失败
五. 命名路由
可以在创建 Router 实例的时候,在 routers 配置中给某个路由设置名称
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
要链接到一个命名路由,可以传一个对象:
<router-link :to='{ name: 'user', params: { userId: 123 }}'>User</router-link>
// 也可以使用 router.push() 方法,这两种方式都会把路由导航到 /user/123 路径
router.push({ name: 'user', params: { userId: 123 }})
六. 命名视图
如果想同时展示(同级)展示多个视图,而不是嵌套展示,则可以使用命名视图,如果 router-view 没有设置名字,那么默认为 default
<router-view></router-view>
<router-view name='a'></router-view>
<router-view name='b'></router-view>
//一个视图使用一个组件渲染,对于同个路由,多个视图就需要多个组件。使用 components(带上 s) 配置
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
七. 重定向和别名
重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:
const router = new VueRouter({
routers: [
{ path: '/a', redirect: '/b'}
]
})
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接受目标路由作为参数
// return 重定向的字符串路径/路径对象
}}
]
})
重定向的意思是,当用户方法 /a 时,URL将会被替换成 /b,然后匹配路由为 /b。
/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,当时路由匹配规则为 /a,就像用户访问 /a 一样
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b'}
]
})
八. 路由组件传参
在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的URL上使用,使用 props 将组件和路由解耦
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User}
]
})
通过 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}
}
]
})
如果 props 被设置为 true,route.params 将会被设置为组件属性
如果 props 是一个对象,将会被按原样设置为组件属性,当 props 是静态的时候有用
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false }}
]
})
可以创建一个函数返回 props,这样便可以将参数转换成另一种类型,将静态值与基于路由的值结合
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
})
// URL /search?q=vue 会将 {query: 'vue'}作为属性传递给 SearchUser 组件
九. 导航守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航,参数或查询的改变并不会触发进入/离开的导航守卫,可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫
可以使用 router.beforeEach 注册一个全局前置守卫
const router = new VueRouter({ ... })
router.beforeEach(to, from, next) => {
//....
}
当一个导航触发时,全局配置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中
每个守卫方法接收三个参数:
- to:即将要进入的目标路由对象
- from:当前导航正要离开的路由
- next:一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数
- next():进行管道中的下一个钩子,如果全部钩子执行完了,则导航状态就是 confirmed(确认的)
- next(false):中断当前的导航。如果浏览器的URL改变了(可能是用户手动或者浏览器后退按钮),那么URL地址会重置到 from 路由对应的地址
- next('/') 或者 next({ path: '/' }):跳转到一个不同的地址。当前导航被中断,然后进行一个新的导航。可以向 - next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项
- next(error):如果传入 next 的参数时一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调
确保要调用 next 方法,否则钩子就不会被 resolved
可以用 router.beforeResolve 注册一个全局解析守卫,与 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就会被调用
也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身
router.afterEach((to, from) => {
// ...
})
可以在路由配置上直接定义路由独享守卫 beforeEnter,这些守卫与全局前置守卫的方法参数时一样的
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
也可以在路由组件内直接定义以下路由导航守卫:
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不能访问this,因为守卫在导航确认以前被调用,此时组件还没有被创建,不过可以通过传一个回调给 next 来访问组件实例,在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。该守卫是唯一支持给 next 传递回调的守卫
next(vm => {
// 通过 vm 访问组件实例
})
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用,而这个钩子就会在这个情况下被调用
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 常用来禁止用户在还未保存修改前突然离开,可以通过 next(false) 来取消
}
}
完整的导航解析流程
- 导航被触发
- 在失活的组件里调用离开守卫
- 调用全局的 beforeEach 守卫
- 在重用的组件里调用 beforeRouteUpdate 守卫
- 在路由配置里调用 beforeEnter
- 解析异步路由组件
- 在被激活的组件里调用 beforeRouteEnter
- 调用全局的 beforeResolve 守卫
- 导航被确认
- 调用全局的 afterEach 钩子
- 触发 DOM 更新
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数
十. 路由元信息
定义路由的时候可以配置meta字段
const router = new VueRouter({
routes: [{
path: '/foo',
component: Foo,
children: [{
path: 'bar',
component: Bar,
meta: { requiresAuth: true }
}]
}]
})
routes配置中的每个路由对象为路由记录,路由记录可以嵌套,当一个路由匹配成功后,它可能匹配多个路由记录。比如上面的路由配置,/foo/bar 这个URL将会匹配父路由及子路由
一个路由匹配到的所有路由记录会暴露为 route.matched 来检查路由记录中的 meta 字段。下面的例子展示在全局导航守卫中检查元字段
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!auth.loggedIn()) { // 如果需要登录授权而没有登录授权
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else next()
} else next()
})
十一. 过渡动效
<router-view> 是基本的动态组件,所以可以用 <transition> 组件给它添加一些过渡效果:
<transition>
<router-view></router-view>
</transition>
该方法会给所有的路由设置一样的过渡效果,如果想让每个路由组件有各自的过渡效果,可以在各自路由组件内使用 <transition> 并设置不同的name
const Foo = {
template: `
<transition name='slide'>
<div class='foo'>...</div>
</transition>
`
}
const Bar = {
template: `
<transition name='fade'>
<div class='bar'>...</div>
</transition>
`
}
还可以基于当前路由与目标路由的变化关系,动态设置过渡效果:
<!-- 使用动态的 transition name -->
<transition :name='transitionName'>
<router-view></router-view>
</transition>
// 接着在父组件内 watch $route 决定使用哪种过渡
watch: {
'$route' (to, from) {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}
十二. 数据获取
在进入某个路由后,需要从服务器获取数据,可以通过两种方式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航
导航完成后获取数据时,会马上导航和渲染组件,然后在组件的 created 钩子中获取数据
export default {
data () {
return {}
},
created () {
//组件创建完后获取数据,此时data已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
methods: {
fetchData () {
// 获取数据
}
}
}
导航完成前获取数据,可以在组件的 beforeRouteEnter 守卫中获取,获取成功后调用 next 方法
beforeRouterEnter (to, from, next) {
getPost(to.params.id, (err, data) => {
next(vm => vm.setData(err, data))
})
},
//路由改变前,组件已经渲染完了
beforeRouteUpdate (to, from, next) {
getPost(to.params.id, (err, data) => {
this.setData(err, data)
next()
})
},
methods: {
setData (err, data) {}
}
十三. 滚动行为
使用前端路由,当切换到新路由时,想要页面滚动到顶部,或者是保持原先的滚动位置,可以使用 scrollBehavior 方法
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到的位置
}
})
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savePosition 当且仅当 popstate导航(通过浏览器的 前进/后退 按钮触发)时才可用
这个方法返回滚动位置的对象信息
- { x: number, y: number}
- { selector: string, offset?: { x: number, y: number }}
如果返回一个falsy,或者是一个空对象,那么不会发生滚动
// 对于所有路由导航,简单的让页面滚动到顶部
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0}
}
如果返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样。如果要模拟滚动到锚点的行为:
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash
}
}
}
异步滚动,可以返回一个Promise来得出预期的位置描述
scrollBehavior (to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 500)
})
}
网友评论