注意: 本文所有示例代码详见:vue-rouer-demo
What | 什么是Vue Router
Vue Router
是Vue.js提供的官方路由管理器,它和Vue.js深度集成,使构建单页面应用非常方便。
Why | 为什么要使用Vue Router
大家打开LorneNote个网站,这是我的一个blog网站,采用传统的开发模式,鼠标右击,在出现的菜单里选择View Page Source 查看资源文件,大家会看到它对应的是一个HTML文件,然后你回到网站里点击归档栏,再次右击查看源文件,你会看到整个页面被重新加载,对应归档的HTML文件,即:
- https://lornenote.com —–> LorneNote HTML文件
- https://lornenote.com/archives/ —–> 归档 | LorneNote HTML文件
也就是说每一个URL对应一个HTML文件,这样每次切换页面的时候我们都需要重新加载我们的页面,非常影响用户体验。
然后就诞生了单页面形式SPA(single page applications)。在单页面模式下,不管我们访问什么页面,都返回index.html
,类似这样:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<title>hello-world</title>
<link href="/app.js" rel="preload" as="script"></head>
<body>
<noscript>
<strong>We're sorry but hello-world doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script type="text/javascript" src="/app.js"></script></body>
</html>
在单文件模式里,我们依然可以跳到不同的HTML文件,但我们一般不会这样做,一个index.html就满足了我们的需求,用户切换URL的时候,不再是重新加载我们的页面,而是根据URL的变化,执行相应的逻辑,数据会通过接口的形式返回给我们,而后做页面更新。Vue Router就是为了解决这样的事情,它可以做这些事情:
- 提供URL和组件之间的映射关系,并在URL变化的时候执行对象逻辑
- 提供多种方式改变URL的API(URL的变化不会导致浏览器刷新)
How | Vue Router 如何使用
核心使用步骤
- 安装并导入VueRouter,调用Vue.use(VueRouter),这样的话可以使用
<router-view>
和<router-link>
等全局组件
1 | // 安装命令 |
- 定义路由组件入口
1 | // App.vue |
- 使用定义好的路由组件配置路由列表
1 | // routes.js |
- 创建路由实例,并通过路由列表初始化路由实例
1 | // main.js |
- 将创建好的路由实例挂载到跟实例,这样我们就可以使用this.$route全局钩子来操作路由管理器为我们提供的一些属性。
1 | // main.js |
注意
this.$router
代表我们刚刚实例化的全局路由实例,它和我们导入router相同,只是为了使用方便,不用在组件中导入了。this.$route
,写法上少了一个r,代表任何组件的当前路由。
路由动态匹配
有时候我们需要映射不同的路由给相同的组件1
2
3
4
5// routes.js
{ path:'/user/:id', component:User}
// HelloWorld.vue
<router-link to="/user/123">张三-single params</router-link><br>
在User组件中,可以以this.$route.params
的形式接收参数id
1
2
3
4
5<template>
<div>User
{{ this.$route.params.id }} // 123
</div>
</template>
动态匹配是以冒号区隔的,我们可以有多个动态段:1
2
3
4
5
6
7
8
9
10
11
12// routes.js
{ path:'/user/:userName/userId/:id', component:User},
// HelloWorld.vue
<router-link to="/user/王五/userId/789">王五-mutiple params</router-link>
// User.vue
<template>
<div>User
{{ this.$route.params }} // { "userName": "王五", "id": "789" }
</div>
</template>
路由routes与path、$route.params三者的关系见下表:
路由模式 | 匹配path | $route.params |
---|---|---|
/user/:userName/userId/:id | /user/王五/userId/789 | { “userName”: “王五”, “id”: “789” } |
除了$route.parmas
,$route
还封装了其他的信息如:$route.query
:用于获取URL中的查询,$route.hash
:用于获取url中#之后的信息(自己写的路径带#,值会包含#,如果是history模式下自动追加的#,则值不包含这个#),如果没有,值为空,如果URL中有多个#,从最后一个开始。
我们重写一下上文的例子:1
2
3
4
5
6
7
8
9
10
11// HelloWorld.vue
<router-link to="/user/Query/userId/101?code=123">Query</router-link><br>
<router-link to="/user/Hash/userId/102#hash=123">Hash</router-link><br>
// User.vue
<template>
<div>User
{{ this.$route.query }} // { "code": "123" }
{{ this.$route.hash }} // #hash=123 原路径:http://localhost:8081/#/user/Hash/userId/102#hash=123
</div>
</template>
由于vue-router使用 path-to-regexp作为路径path的匹配引用,所以我们可以使用正则表达式作为路径:1
2
3
4
5
6
7
8
9
10
11
12// routes.js
{ path:'/icon-:flower(\\d+).png', component:User},
// HelloWorld.vue
<router-link to="/icon-flower123">正则匹配</router-link><br>
// User.vue
<template>
<div>User
{{ this.$route.hash }} // /icon-flower123 原路径:http://localhost:8081/#/icon-flower123
</div>
</template>
我们可以使用星号(*
)来代表通配符,只有*
则匹配所有,常代表404页面,因为路由的优先级是优先配置优先匹配,相同的组件谁在前面先匹配谁
。所以404页面通常写在最后一个:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// routes.js
const routes = [
// ...
{ path:'/user-*', component:User},
{ path:'*', component:User}
]
// HelloWorld.vue
<router-link to="/user-admin">*Admin</router-link><br>
<router-link to="/none">404</router-link><br>
// User.vue--*Admin
<template>
<div>User
{{ this.$route.params.pathMatch }} // admin
</div>
</template>
// User.vue--404
<template>
<div>User
{{ this.$route.params.pathMatch }} // /none
</div>
</template>
如上:
当使用*
号路由时,一个参数pathMatch
会自动追加给$route.params
,它匹配星号后面的部分。
嵌套路由
真实的应用中常常用到组件之间的嵌套,使用vue-router可以很简单的表达这种嵌套关系,我们可以在子组件中嵌套使用<router-view>
,如:
1 | // NestedRoutes.vue |
为了做嵌套渲染,我们使用children
操作符在路由中做结构配置:1
2
3
4
5
6
7// routes.js 嵌套路由
{path:'/nestedroutes', component: NestedRoutes, name: 'nestedroutes',
children: [
{path:'profile', component: resolve => require(['./components/nested-routes/Profile'],resolve)}, // 匹配 /nestedroutes/profile
{path:'archive', component: resolve => require(['./components/nested-routes/Archive'],resolve)} // 匹配 /nestedroutes/archive
]
},
嵌套路由以/
为根路径,拼接路径的剩余部分,children其实和routes的最外层一样,是数组,所以我们可以根据需要继续嵌套。
当我们访问/nestedroutes
的时候,不会在这里渲染任何内容,因为没有子路由,如果我们想要渲染,可以在路由里配置一个空的path的子路由:1
2
3
4
5
6
7
8// 嵌套路由
{path:'/nestedroutes', component: NestedRoutes, name: 'nestedroutes',
children: [
{path:'', component: resolve => require(['./components/nested-routes/NestedRoutesHome'],resolve)},
{path:'profile', component: resolve => require(['./components/nested-routes/Profile'],resolve)},
{path:'archive', component: resolve => require(['./components/nested-routes/Archive'],resolve)}
]
},
编程式的导航
除了以<router-link>
的标签形式导航之外,我们还可以使用编程的方式导航,vue-router为我们提供了对应的实例方法this.$router.push
。
这个方法会将URL页面推到历史堆栈,所以当我们点击浏览器的返回按钮的时候指向前一个URL。当我们点击<router-link>
的时候,内部其实调用的是router.push(...)
,所以这两个形式功能是等同的。
1 | router.push(location, onComplete?, onAbort?) |
location
:导航位置,是一个字符串path或位置描述符对象,
第二个和第三个为可选参数,导航执行完成会执行这两个参数。onComplete
导航执行完会执行这个参数(所有的异步钩子都被执行完之后)
使用方式如下:1
2
3
4
5
6
7
8 <!--path-->
<button @click="$router.push('programmatic-navigation')">Path</button><br>
<!--object-->
<button @click="$router.push({path:'programmatic-navigation'})">Object</button><br>
<!-- named route-->
<button @click="$router.push({name:'programmatic-navigation', params: { id:123 }})">NamedRoute</button><br>
<!--with query reuslting in /programmatic-navigation?id=123-->
<button @click="$router.push({path:'programmatic-navigation', query:{ id:123 }})">Query</button><br>
注意:
如果使用了path
,params
就被忽略了,但上面的query
不会被忽略。所以如果我们要传递参数,可以用两种方式,一种是提供一个name路由,一种是在path里面手动拼接参数:
1 | // routes.js |
这个规则也同样适用于router-link
的to
属性。
如果我们的当前路径和跳转路径相同,那么我们需要在beforeRouteUpdate方法中响应数据更新:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28ProgrammaticNavigation.vue
<template>
<div>
<a href="/">←返回</a><br>
<p>ProgrammaticNavigation</p>
<pre>{{ JSON.stringify(params) }}</pre>
<button @click="$router.push({ path:`/programmatic-navigation/params=/${ 456 }`})">WidthParmas03</button><br>
</div>
</template>
<script>
export default {
name: "ProgrammaticNavigation",
data() {
return {
params:this.$route.params
}
},
beforeRouteUpdate (to, from, next) {
if (to.path == '/programmatic-navigation/params=/456') {
this.params = to.params
}
}
}
</script>
router.replace
router.replace
与router.push
类似,唯一不同的是它不是推送到一个新的历史页,而是替换当前页。
router.go(n)
这个方法和window.history.go(n)
类似,n
用整数表示,表示在历史页中向前后向后走多少页。1
2
3
4
5
6// 后退一页
router.go(-1)
// 前进一页
router.go(1)
// 如果没有,导航失败,页面保持不变
router.go(10)
操作History
路由的router.push
,router,replace
和router.go
对应window.history.pushState
, window.history.replaceState
和 window.history.go
, 他们模仿的是window.historyAPIs。
Vue Router的导航方法(push
, replace
, go
)在所有的路由模式下都可以工作(history
,hash
和abstract
)。
命名路由
有时候给路由一个名字更方便,用法也很简单:1
2
3
4
5
6
7
8
9
10// routes.js
{
path:'/named-routes/:id',
component: resolve => require(['./components/named-routes/NamedRoutes'],resolve),
name:'named-routes'
},
// HelloWorld.vue
<router-link :to="{name: 'named-routes', params: { id:123 }}">named-routes router-link</router-link><br>
<button @click="$router.push({name: 'named-routes', params: { id:123 }})">named-routes router.push</button>
这两种方式都是对象传递,注意to前面要加冒号的,表示内部是对象表达式而不是单纯的字符串。
命名视图
有时候我们可能需要在同一页展示多个视图,而不是做视图嵌套,比如说主页面或者是sidebar侧边栏。这个时候我们就会用到命名视图,也就是说在同一页面下使用多个<router-view>
,给每个<router-view>
一个不同的名字,如果没给名字,默认名为default
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 // routes.js 命名View
{
path:'/named-views',
component: resolve => require(['./components/named-views/NamedViews'],resolve),
children: [
{
path:'',
components: {
default:resolve => require(['./components/named-views/ViewA'],resolve),
b: resolve => require(['./components/named-views/ViewB'],resolve),
c: resolve => require(['./components/named-views/ViewC'],resolve),
},
}
]
},
// NamedViews.vue
<template>
<div>
<a href="/">←返回</a><br>
<p>NamedViews</p>
<router-view></router-view>
<router-view name="b"></router-view>
<router-view name="c"></router-view>
</div>
</template>
嵌套命名视图
我们可以在嵌套视图里使用命名视图创造更复杂的布局。我们来看一个例子:1
2
3
4
5
6
7
8
9// NestedNamedViews.vue
<template>
<div>
<p>NestedNamedViews</p>
<ViewNav/>
<router-view></router-view>
<router-view name="b"></router-view>
</div>
</template>
这里:
NestedNamedViews
本身是View组件ViewNav
是一个常规的组件router-view
内部是被嵌套的视图组件
路由配置这样来实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// routes.js
{ path: '/nested-named-views',
component: resolve => require(['./components/named-views/NestedNamedViews'], resolve),
children: [
{
path:'profile',
component: resolve => require(['./components/nested-routes/Profile'],resolve)
},
{
path:'archive',
components: {
default:resolve => require(['./components/named-views/ViewA'],resolve),
b: resolve => require(['./components/named-views/ViewB'],resolve),
}
}
]
},
转发和别名
转发
是我们可以在访问a的时候跳转b。
支持路径和命名访问两种形式:1
2
3
4
5
6
7// routes.js
{
path:'/orignal01' ,redirect: '/forward'
},
{
path:'/orignal02' ,redirect: {name: 'forward'}
},
别名是这样的,如果/b
是组件a
的别名,那意味着我们访问/b
的时候,匹配的组件依然是a
:1
2
3
4
5
6
7// routes.js
{
path: '/forward',
name: 'forward',
component: resolve => require(['./components/redirect-views/RedirectViews'], resolve),
alias: '/alias'
},
传递Props给路由组件
为了使路由的组件更加灵活,vue支持在路由组件中传递Props,使用props
操作符。支持三种模式:
布尔值模式
当props
设置为true
的时候,route.params
将被设置作为组件的props。1
2
3
4
5
6// routes.js
{
path: '/props-to-route/:id', // http://10.221.40.28:8080/props-to-route/123
component: resolve => require(['./components/props-route/PropsRoute'], resolve),
props: true
},
对象模式
当props是一个对象的时候,这个对象也一样会被设置给组件的props,这在props为固定值的时候很有用:1
2
3
4
5
6// routes.js
{
path: '/static', // http://10.221.40.28:8080/static
component: resolve => require(['./components/props-route/PropsRoute'], resolve),
props: {name: 'static'}
},
函数模式
我们可以创建一个函数返回props,这可以让你转化参数为其他类型,经静态值与基于路由的值结合,等等。1
2
3
4
5
6// routes.js
{
path: '/function-mode', // http://10.221.40.28:8080/function-mode?keyword=%27beijing%27
component: resolve => require(['./components/props-route/PropsRoute'], resolve),
props: (route) => ({ query: route.query.keyword})
},
如果URL为/function-mode?keyword='beijing'
将传递{query: 'beijing'}
作为组件的props。
注意:
props函数是无状态的,仅仅计算路由的改变。如果需要状态去定义props,那么Vue官方建议封装一个组件,这样vue就能够对状态做出反应。
路由进阶
导航警卫
这是vue-router提供的一些控制路由进程的函数,有三种表现方式:全局定义,路由内定义和组件中定义。
全局定义
分为前置警卫、解析警卫和后置钩子。我们依次看一下:
1.前置警卫1
2
3router.beforeEach((to, from, next) => {
// ...
})
无论哪个导航先触发之前都会先调用它,警卫的解析可能是异步的,所以在所有的钩子解析完成之前,导航处于悬停状态(pending)
。实践中经常在这里判断是否携带了进入页面的必要信息,否则做跳转。(比如通过URL地址栏手动输入地址非法进入子页面,需要跳转到登录页让用户登录)
注意:别忘了写
next
函数,否则钩子函数将不会被解析。
2.全局解析警卫1
2
3router.afterEach((to, from) => {
// ...
})
和前置警卫一样,只不过是在导航确认之前,所有组件内警卫和异步路由组件被解析之后会立即调用。
3.全局后置钩子
这些钩子和警卫不同的是没有next函数,并且不影响导航。1
2
3router.afterEach((to, from) => {
// ...
})
路由独享的警卫
我们可以在路由的配置对象里配置:beforeEnter
1
2
3
4
5
6
7
8
9
10
11const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
这个和全局前置警卫作用相同。
组件内警卫
在组件内可以定义一些路由导航警卫(会被传递给路由配置表):
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
beforeRouteEnter
不能访问this,因为组件还没有被创建。然后我们可以在next回调里访问,在导航被确认时,组件实例会被当做参数传递给这个回调:1
2
3
4
5beforeRouteEnter (to, from, next) {
next(vm => {
// access to component instance via `vm`
})
}
而在beforeRouteUpdate
和beforeRouteLeave
,this
已经可用。所以next回调没有必要因此也就不支持了。1
2
3
4
5beforeRouteUpdate (to, from, next) {
// just use `this`
this.name = to.params.name
next()
}
离开警卫
常常用于阻止用户意外离开未经保存的内容,导航可用通过next(false)
取消。1
2
3
4
5
6
7
8beforeRouteLeave (to, from, next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
完整的导航解析流程
- 导航被触发
beforeRouteLeave
警卫在失活组件被调用- 全局的
beforeEach
警卫被调用 - 在可复用的组件内调用
beforeRouteUpdate
- 在路由配置中调用
beforeEnter
- 解析异步路由组件
- 在激活组件中调用
beforeRouterEnter
- 调用全局beforeResolve警卫
- 导航被确认
- 调用全局
afterEach
钩子 - DOM更新被触发
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
路由Meta字段
定义路由的时候可以包含一个meta字段:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
怎么样使用这个字段呢?
首先,在routes
配置里的每一个路由对象称为路由记录
。路由可能会被嵌套,因此当一个路由被匹配的时候,可能会匹配超过一个记录。
例如,上面的路由配置,/foo/bar
将匹配父路由记录和子路由记录两个路由配置。
所有的被匹配的路由记录都被导出在$route
对象(也在导航警卫的路由对象里)作为$route.matched
数组。因此我们需要迭代这个数组来找到路由记录里的meta字段。
在全局导航警卫中检查meta字段的一个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
过渡
简单说就是给路由加一些过渡效果。有几种方式可以实现:
1.在统一的router-view
入口给一个一致的过渡1
2
3<transition>
<router-view></router-view>
</transition>
2.在每一个组件内部给一个特别的过渡1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const Foo = {
template: `
<transition name="slide">
<div class="foo">...</div>
</transition>
`
}
const Bar = {
template: `
<transition name="fade">
<div class="bar">...</div>
</transition>
`
}
3.还可以根据当前路由和目标路由之间的关系设计动态过渡1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38<template>
<div>
<div>
<router-link to="subviewa">subviewa</router-link><br>
<router-link to="subview-b">subview-b</router-link><br>
<p>Home</p>
<transition :name="transitionName">
<router-view></router-view>
</transition>
</div>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
transitionName: 'slide-left'
}
},
beforeRouteUpdate (to, from, next) {
this.transitionName = to.path < from.path ? 'slide-right' : 'slide-left'
next()
},
}
</script>
<style scoped>
.slide-left-enter, .slide-right-leave-active {
opacity: 0;
transform: translateX(10px);
}
.slide-left-leave-active, .slide-right-enter {
opacity: 0;
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
</style>
数据获取
在实践中,很多时候在进入一个新的路由页面时,我们都是需要从服务器请求数据的,有两种方式:
- 在导航之后获取:首先导航到新的页面,在导航的生命周期钩子中(比如
created
方法)做数据获取。 - 在导航之前获取:在路由导航进入警卫之前获取数据,数据获取完成之后执行导航。
技术上都可以实现,用哪种取决于用户体验的目标。
导航之后获取数据
使用这种方式,我们会立即导航到新的页面,渲染组件,在组件的created钩子中渲染组件。当获取数据是我们可以展示一个loading的状态,并且每一页可以有自己的loading视图。
我们看一个代办事项的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68<template>
<div>
<div v-if="loading">
Loading
</div>
<div v-if="error">
{{ error }}
</div>
<div v-if="list" >
<div v-for=" item in list" :key="item.id">
<p>{{ item.title }}</p>
</div>
</div>
</div>
</template>
<script>
import TodoAPI from '../api/todo';
export default {
name: "TodoList",
data () {
return {
loading: false,
list: null,
error: null
}
},
created () {
// 组件创建时获取数据
this.fetchData()
},
watch: {
// 路由改变的时候再次调用方法
'$route': 'fetchData'
},
methods: {
fetchData () {
this.error = this.list = null
// 模拟网络请求
TodoAPI.getTodoList(
(todolist) => {
this.loading = false,
this.list = todolist
},
(error)=>{
this.loading = false,
this.error = error
})
}
}
}
</script>
// 网络请求API -- todo.js
const _todoList = [
{"id": 1, "title": "购物"},
{"id": 2, "title": "回复邮件" }
]
export default ({
getTodoList (cb,errorCb) {
setTimeout(() => {
Math.random() > 0.5
? cb(_todoList)
: errorCb("数据请求失败")
},1000)
}
})
滚动行为
当我们使用vue-router的时候,我们可能想要打开新页再在一个位置,或使返回历史页保持在上次浏览的位置。vue-router
允许我们自定义导航行为。
注意
这个功能仅仅在浏览器支持history.pushState的时候可以用。
当我们创建一个路由实例的时候,我们可以创建一个scrollBehavior
函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35const scrollBehavior = function (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
const postion = {}
if (to.hash) {
postion.selector = to.hash
if (to.hash === '#anchor2') {
postion.offset = {y: 100 }
}
if (/^#\d/.test(to.hash) || document.querySelector(to.hash)) {
return postion
}
return false
}
return new Promise(resolve => {
if (to.matched.some(m => m.meta.scrollToTop)) {
postion.x = 0
postion.y = 0
}
this.app.$root.$once('triggerScroll', () => {
resolve(postion)
})
})
}
}
const router = new VueRouter({
mode: 'history',
scrollBehavior,
routes,
})
这里面接收to
,from
两个路由对象,第三个参数savedPosition
,第三个参数仅仅在popstate
导航中可用(浏览器的向前/后退按钮被触发的时候)。
这个对象返回滚动位置对象,格式如:
{x: nubmer, y:number }
{selector: string, offset? : { x: number, y: number}}
(offset 仅仅被支持在2.6.0+以上)
如果是空值或无效值,则不会发出滚动。
1 | scrollBehavior (to, from, savedPosition) { |
如果返回savedPosition
,和我们使用浏览器的向前向后按钮一下,如果有历史滚动页,会返回到历史页查看的位置,如果返回{ x: 0, y: 0 }
则滚动到顶部。
在开始的例子里,我们可以这样模拟滚动到某处的行为:1
2
3
4
5
6
7
8
9if (to.hash) {
postion.selector = to.hash
if (to.hash === '#anchor2') {
postion.offset = {y: 100 }
}
// ...
}
以下我们返回了一个Promise
,来返回滚动的位置1
2
3
4
5
6
7
8
9
10
11
12return new Promise(resolve => {
// 如果匹配scrollToTop,我们就返回顶部
if (to.matched.some(m => m.meta.scrollToTop)) {
postion.x = 0
postion.y = 0
}
// 如果空值或无效值,返回当前滚动位置
this.app.$root.$once('triggerScroll', () => {
resolve(postion)
})
})
懒加载路由
当使用一个捆绑器构建应用时,JavaScript包会变得非常大,因此影响页面加载的时间。如果我们可以把每一个路由组件分离单独的块,等浏览页面时再加载他们会效率比较高。
Vue的异步组件功能和webpack的代码分离功能可以很容易的做到这一点。
首先,可以将异步组件定义为返回Promise的工厂函数(应解析为组件本身):1
const Foo = () => Promise.resolve({ /* component definition */ })
其次,在webpack2,我们可以使用动态导入语法去表示代码分离点:1
import('./Foo.vue') // returns a Promise
注意
如果你使用Babel,你需要添加syntax-dynamic-import以便Babel可以正确解析。
把这两个步骤联合起来,我们就可以通过webpack自定定义一个异步的代码分离组件,所以我们可以把我们的路由配置修改为:1
2
3{
path: '/lazy-route', component: () => import('./components/lazy-route/LazyHome')
}
在同一个块中分组组件
有时,我们想要把所有组件分组到相同的异步块中,为了实现这个,在webpack > 2.4中,我们可以使用特殊的注释语法提供命名块:1
2
3
4
5
6{
path: '/lazyTwo', component: () => import(/* webpackChunkName: "lazy" */ './components/lazy-route/LazyTwo'),
children: [
{path: 'lazyThree', component: () => import (/* webpackChunkName: "lazy" */ './components/lazy-route/LazyThree')}
]
},
webpack将使用相同块名称的任何异步模块分组到相同的异步块中。
参考:Vue Router