在 Vue 的学习中,VueRouter 是 Vue.js 官方的路由管理器,该文档是对 VueRouter 官方文档的知识的学习笔记,包括了 VueRouter 的基本使用以及进阶部分的知识,具体查看文档;
VueRouter官方文档笔记
1. 起步
1.1 html 框架
1 | <script src="https://unpkg.com/vue/dist/vue.js"></script> |
1.2 JavaScript 代码
1 | // 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter) |
通过注入路由器router
对象到Vue
对象中,我们可以在Vue的任何组件内通过 this.$router
访问路由器,也可以通过 this.$route
访问当前路由:
1 | // Home.vue |
2. 动态路由匹配(params 参数)
有时需要把某种模式匹配到的所有路由,全都映射到同个组件,可以在 vue-router
的路由路径中使用“动态路径参数”(dynamic segment
) 来达到这个效果;例如,有一个 User
组件,对所有 ID
各不相同的用户,都要使用这个组件来渲染:
1 | // 注册路由 |
可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params
中,例如:
模式 | 匹配路径 | $route.params |
---|---|---|
/user/:username | /user/evan | { username: ‘evan’ } |
/user/:username/post/:post_id | /user/evan/post/123 | { username: ‘evan’, post_id: 123 } |
2.1 响应路由参数的变化(路由参数改变,组件的复用)
若使用路由参数时,例如从 /user/foo
导航到 /user/bar
,原来的组件实例会被复用;因为两个路由都渲染同个组件,复用显得更加高效;这也意味着组件的生命周期钩子不会再被调用,复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch
(监测变化) $route
对象:
1 | // watch (监测变化) $route 对象 |
2.2 高级匹配模式
vue-router
使用 path-to-regexp 作为路径匹配引擎,所以支持很多高级的匹配模式,例如:可选的动态路径参数、匹配零个或多个、一个或多个,甚至是自定义正则匹配;
2.3 匹配优先级
同一个路径可以匹配多个路由,此时匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
3. 嵌套路由
下面是一个嵌套路由的实例,从设计的结构到实现的代码:
1 | /user/foo/profile /user/foo/posts |
1 | // 根节点app,这里的 <router-view> 是最顶层的出口,渲染最高级路由匹配到的组件 |
1 | // 定义动态路由参数,并在组件中使用 |
1 | // 在 User 组件的模板添加一个 <router-view>【一个被渲染组件同样可以包含自己的嵌套<router-view>】 |
1 | // 在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置: |
注意:以 /
开头的嵌套路径会被当作根路径; 这让你充分的使用嵌套组件而无须设置嵌套的路径。
4. 编程式的导航
4.1 router.push()
声明式导航 | 编程式导航
— | — | —<router-link :to="...">
| router.push(...)
当点击 <router-link>
时,这个方法会在内部调用router.push(...)
,因而两个是等同关系的;
编程式导航语法: router.push(location, onComplete?, onAbort?)
在 router.push
或 router.replace
中提供 onComplete
和 onAbort
回调作为第二个和第三个可选参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用;
注意:在 Vue 实例内部,你可以通过 $router
访问路由实例
;因此可以使用 this.$router.push
方法;
例如:
1 | // 字符串 |
注意:如果提供了 path,params
会被忽略,上述例子中的 query
并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name
或手写完整的带有参数的 path
,同样的规则也适用于 router-link
组件的 to
属性;
1 | const userId = 123 |
4.2 router.replace()
声明式导航 | 编程式导航
— | — | —<router-link :to="..." replace>
| router.replace(location, onComplete?, onAbort?)
该方法跟 router.push
很像,唯一的不同就是,它不会向 history
添加新记录,而是跟它的方法名一样是替换掉当前的 history
记录。
4.3 router.go()
所有语法:router.go(n)
; 这个方法的参数是一个整数,意思是在 history
记录中向前或者后退多少步,类似 window.history.go(n)
;
1 | // 在浏览器记录中前进一步,等同于 history.forward() |
4.4 操作 History
router.push
、 router.replace
和 router.go
跟 window.history.pushState
、 window.history.replaceState
和 window.history.go
好像, 实际上它们确实是效仿 window.history API
的,参考浏览器历史纪录API接口 Browser History APIs;
同时,Vue Router
的导航方法 (push、 replace、 go
) 在各类路由模式 (history、 hash 和 abstract
) 下表现一致;
5. 命名路由
在创建 Router
实例的时候,在 routes
配置中给某个路由设置名称(name
属性);
1 | const router = new VueRouter({ |
要链接到一个命名路由,可以给 <router-link />
的 to
属性传一个对象,如下:
1 | <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link> |
调用 router.push()
也是一样的道理:
1 | router.push({ name: 'user', params: { userId: 123 }}) |
6. 命名视图
若想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default
;
1 | <router-view class="view one"></router-view> |
其中一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s):
1 | const router = new VueRouter({ |
6.1 嵌套命名视图
对于命名视图创建嵌套视图的复杂布局,也需要命名用到的嵌套 router-view
组件,如下面板为例:
1 | /settings/emails /settings/profile |
Nav
只是一个常规组件;UserSettings
是一个视图组件;UserEmailsSubscriptions、UserProfile、UserProfilePreview
是嵌套的视图组件;
UserSettings
组件的 <template>
部分代码:
1 | <!-- UserSettings.vue --> |
路由配置规则代码如下:
1 | { |
7 重定向和别名
7.1 重定向
重定向也是通过 routes
配置来完成,设置redirect
属性的值;下面例子是从 /a
重定向到 /b
:
1 | const router = new VueRouter({ |
注意:导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上;在下面这个例子中,为 /a 路由添加一个 beforeEach
或 beforeLeave
守卫并不会有任何效果;
7.2 别名
别名的含义:例如 /a
的别名是 /b
,意味着,当用户访问 /b
时, URL
会保持为 /b
,但是路由匹配则为 /a
,就像用户访问 /a
一样;“别名”的功能让你可以自由地将 UI
结构映射到任意的 URL,而不是受限于配置的嵌套路由结构;
1 | const router = new VueRouter({ |
8. 路由组件传参
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性;因而使用 props
将组件和路由解耦,取代与 $route
的耦合:
1 | const User = { |
通过 props 解耦:
1 | const User = { |
8.1 布尔模式
如果 props
被设置为 true
,route.params
将会被设置为组件属性;
8.2 对象模式
如果 props
是一个对象,它会被按原样设置为组件属性;当 props
是静态的时候有用;
1 | const router = new VueRouter({ |
8.3 函数模式
可以创建一个函数返回 props
,这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等;
1 | const router = new VueRouter({ |
9. HTML5 History 模式
vue-router
默认 hash
模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载;若不想要很丑的 hash
,我们可以用路由的 history
模式,这种模式充分利用 history.pushState API
来完成 URL 跳转而无须重新加载页面:
1 | const router = new VueRouter({ |
当使用 history
模式时, URL
就像正常的 url,例如 http://yoursite.com/user/id
;不过这种模式需要后台配置支持;要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html
页面,这个页面就是你 app 依赖的页面;
9.1 后端配置例子
原生 Node.js
1 | const http = require('http') |
9.2 警告
这么做以后,你的服务器就不再返回 404
错误页面,因为对于所有路径都会返回 index.html
文件;为避免这种情况,应该在 Vue
应用里面覆盖所有的路由情况,然后在给出一个 404 页面:
1 | const router = new VueRouter({ |
10. 导航守卫
vue-router
提供的导航守卫主要用来通过跳转或取消
的方式守卫导航,参数或查询的改变
并不会触发进入/离开的导航守卫
;可以通过观察 $route
对象来应对这些变化,或使用 beforeRouteUpdate
的组件内守卫;
10.1 全局守卫
使用 router.beforeEach
注册一个全局前置守卫:
1 | const router = new VueRouter({ ... }) |
to: Route
: 即将要进入的目标路由对象
;from: Route
: 当前导航正要离开的路由;next: Function
: 一定要调用该方法来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)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
确保要调用 next
方法,否则钩子就不会被 resolved
;
10.2 全局解析守卫(2.5.0 新增)
用 router.beforeResolve
注册一个全局守卫;这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
10.3 全局后置钩子
用 router.afterEach
注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
1 | router.afterEach((to, from) => { |
10.4 路由独享的守卫
可以在路由配置上直接定义 beforeEnter
守卫,参数与全局前置守卫的方法参数是一样的:
1 | const router = new VueRouter({ |
10.5 组件内的守卫
可以在路由组件内直接定义以下路由导航守卫:
- beforeRouteEnter
- beforeRouteUpdate (2.2 新增)
- beforeRouteLeave
1 | const Foo = { |
beforeRouteEnter:
beforeRouteEnter
守卫 不能 访问 this
,是因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建;不过可以通过传一个回调给 next
来访问组件实例;在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:
1 | beforeRouteEnter (to, from, next) { |
beforeRouteUpdate:
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫;对于 beforeRouteUpdate
和 beforeRouteLeave
来说, this
已经可用了,所以不支持传递回调:
1 | beforeRouteUpdate (to, from, next) { |
beforeRouteLeave:
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false)
来取消。
1 | beforeRouteLeave (to, from , next) { |
10.6 完整的导航解析流程
- 导航被触发;
- 在失活的组件里调用离开守卫;
- 调用全局的
beforeEach
守卫; - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+); - 在路由配置里调用
beforeEnter
; - 解析异步路由组件;
- 在被激活的组件里调用
beforeRouteEnter
; - 调用全局的
beforeResolve
守卫 (2.5+); - 导航被确认;
- 调用全局的
afterEach
钩子; - 触发 DOM 更新;
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数;
11. 路由元信息
定义路由的时候可以配置 meta
字段:
1 | const router = new VueRouter({ |
在routes
配置中的每个路由对象为 路由记录
;路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录;一个路由匹配到的所有路由记录会暴露为 $route
对象 (还有在导航守卫中的路由对象) 的 $route.matched
数组;因此,我们需要遍历 $route.matched
来检查路由记录中的 meta
字段,如下面例子展示在全局导航守卫中检查元字段:
1 | router.beforeEach((to, from, next) => { |
12. 过渡动效
在 <router-view>
动态组件中使用 <transition>
组件给它添加一些过渡效果:
1 | <transition> |
Transition 的所有功能 都能使用;
12.1 单个路由的过渡
在 <router-view>
动态组件中使用 <transition>
组件会给所有路由设置一样的过渡效果;若想让每个路由组件有各自的过渡效果,可以在各路由组件内使用 <transition>
并设置不同的 name
:
1 | const Foo = { |
12.2 基于路由的动态过渡
可以基于当前路由与目标路由的变化关系,动态设置过渡效果:
1 | <!-- 使用动态的 transition name --> |
1 | // 接着在父组件内 |
13. 数据获取
进入某个路由后,需要从服务器获取数据;例如,在渲染用户信息时,你需要从服务器获取用户的数据;可以通过下面两种方式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据;在数据获取期间显示“加载中”之类的指示;
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航;
13.1 导航完成后获取数据
当使用这种方式时,我们使用导航和渲染组件,然后在组件的 created
钩子中获取数据;这让我们有机会在数据获取期间展示一个 loading
状态,还可以在不同视图间展示不同的 loading
状态:
1 | <template> |
1 | export default { |
13.2 在导航完成前获取数据
通过这种方式,是在导航转入新的路由前获取数据;一般在接下来的组件的 beforeRouteEnter
守卫中获取数据,当数据获取成功后只调用 next
方法:
1 | export default { |
在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示;如果数据获取失败,同样有必要展示一些全局的错误提醒。
14. 滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样;在 vue-router
中可以自定义路由切换时页面如何滚动;
注意: 这个功能只在支持 history.pushState
的浏览器中可用;
在创建 Router
实例时提供一个 scrollBehavior
方法:
1 | const router = new VueRouter({ |
下面对该方法的使用举例:
所有路由导航,简单地让页面滚动到顶部:
1
2
3scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}返回
savedPosition
,在按下后退/前进
按钮时,就会像浏览器的原生表现那样:
1 | scrollBehavior (to, from, savedPosition) { |
- 模拟滚动到锚点的行为:
1 | scrollBehavior (to, from, savedPosition) { |
14.1 异步滚动
返回一个 Promise
来得出预期的位置描述:
1 | scrollBehavior (to, from, savedPosition) { |
15. 路由懒加载
结合 Vue
的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载:
- 将异步组件定义为返回一个
Promise
的工厂函数 (该函数返回的Promise
应该resolve
组件本身):const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
; - 在 Webpack 2 中,我们可以使用
动态 import
语法来定义代码分块点 (split point):import('./Foo.vue') // 返回 Promise
; - 结合上述两者就是如何定义一个能够被 Webpack 自动代码分割的异步组件:
const Foo = () => import('./Foo.vue')
;
15.1 把组件按组分块
把某个路由下的所有组件都打包在同个异步块 (chunk
) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name
(需要 Webpack > 2.4);Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中:
1 | const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue') |