vue2.0一起在懵逼的海洋里越陷越深(五)

前言

本文章系列:vue2.0一起在懵逼的海洋里越陷越深 (http://leenty.com/tags/vuejs/)
之前的几篇文章的说了vue-router以及vuex的简单使用,这次就开始组合这两者进行实际应用。
场地呢就在我的vue2.0Demo,这是源码地址,觉得靠谱的话欢迎加星跟随,有问题欢迎评论和指正😃
OK交代完毕,说下这回要完成的内容。
这回要做的是一个带缩放功能的滑动菜单(就是一个侧拉栏)和文章列表,以及文章的展示路由
使用vuex做状态管理控制css完成动画过度效果以及使用vue-router的嵌套路由功能。

滴滴滴!上车请刷卡!

正文

首先,我们先把核心功能完成,也就是vue-router的嵌套路由功能

嵌套路由功能

有些时候我们会遇到一个模板重复使用的情况,注意我说的并不是一个子模板在其父模板上的重复,而是多个页面之间的使用到相同的父模板。
就好比有这么几个路径

  • vue2.leenty.com/article/demo1
  • vue2.leenty.com/article/demo2
  • vue2.leenty.com/article/demo3
    可以发现其中共有的部分vue2.leenty.com/article
    1
    2
    3
    4
    5
    6
    7
    8
    /article/demo1 /article/demo2 /article/demo3
    +------------------+ +-----------------+ +-----------------+
    | article | | article | | article |
    | +--------------+ | | +-------------+ | | +-------------+ |
    | | demo1 | | +-----> | | demo2 | | +-----> | | demo3 | |
    | | | | | | | | | | | |
    | +--------------+ | | +-------------+ | | +-------------+ |
    +------------------+ +-----------------+ +-----------------+

没错,这个路径指向的就是父模板,他在多个子路由里被重复的渲染,如果我们没有对其做嵌套路由的处理,结果将是不仅我们写的累,vuejs渲染的也多余,因为这个模板被反复的使用却每次都被重新渲染。
so,我们开始做嵌套路由吧!
打开路由列表文件(routes.js)
tips:不要把routes.js和项目里的router.js搞混咯,一个是路由列表文件routes.js,一个是路由导出文件router.js
在里面我们假定有一个基础的路由(这个文件在本系列第二章里有讲到)

1
2
3
4
5
6
7
8
9
10
11
const routes = [
{
name: 'Article',
path: '/article',
component: require('./components/Article.vue')
// 这里因为书写以及以后做异步组件的方便,将原先的import导入的方式改成了require的方式
// 这么做不会实质上没和之前没有变化的,只是单纯的写法不同。
}
]
export default routes

好,接下来我们加入子路由,
目前本系列已经有五篇文章,那么现在一一加入进去。
当前的项目路径

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
src
├── main.js # vuejs应用入口
├── router.js # 路由导出文件
├── routes.js # 路由列表文件
├── App.vue # vuejs应用根模板
├── components
│ ├── Home.vue
│ ├── Article.vue
│ ├── Demo.vue
│ ├── ... # 存放各个路由入口文件
│ └── templates # 存放模板
│ ├── AppArticleList.vue # 文章列表
│ └── ...
└── store
│ ├── index.js # 组装模块并导出 store 的地方
│ ├── actions.js # 根级别的 action
│ ├── mutations.js # 根级别的 mutation
│ ├── types.js # mutation命名空间
│ └── modules
│ ├── demo.js # demo模块
│ └── status.js # 全局应用状态模块
└── md # 存放md文件
└── articles # 文章路径
├── vue2-1.vue
├── vue2-2.vue
├── vue2-3.vue
├── vue2-4.vue
└── vue2-5.vue
(这里为了讲解方便,会将.md文件替换为.vue文件,)
(效果是一样的只是怕读者有些乱,.vue文件你们尝试的时候可以写些东西)

嵌套路由时会在原有路由对象的基础上添加一个children的节点
代码如下:

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
const routes = [
{
name: 'Article',
path: '/article',
// 父路由上尽量写绝对路径,不然可能会引起一些麻烦,
// 有捣(xia)鼓(gao)精神的同学可以试试
component: require('./components/Article.vue'),
children: [
// children节点:传入一个子路由数组,格式与父路由是一样的
{
name: 'vue2_1',
path: 'vue2_1',
// path: 导入的路径是相对路径,你不需要写全,如下的path将会被vuejs理解为:
// '/article/vue2_1'
// *** 注意: ***
// 如果你写的是这样的相对路径,就千万别在前面加'/',不然就意味着这是一个绝对路径
component: require('./md/articles/vue2-1.vue')
},
{
name: 'vue2_2',
path: '/article/vue2_2',
// 演示一个绝对路径,这样子的路径会被vuejs理解为其本意'/article/vue2_2'
// 如果这里只写了'/vue2_2'的话,vuejs就会把路由理解为'/vue2_2',
// 不过不用担心他显示上的问题,只是url会有些有趣的区别
// 你会发现虽然url变短了,但是父模板依然正常的显示着。
component: require('./md/articles/vue2-2.vue')
},
{
name: 'vue2_3',
path: 'vue2_3',
component: require('./md/articles/vue2-3.vue')
},
{
name: 'vue2_4',
path: 'vue2_4',
component: require('./md/articles/vue2-4.vue')
},
{
name: 'vue2_5',
path: 'vue2_5',
component: require('./md/articles/vue2-5.vue')
}
]
}
]
export default routes

嗯,好了,这样嵌套路由就建立好了

对侧拉栏做状态管理

打开src/store/modules/status.js
代码如下

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
import * as types from '../types'
// 引入命名空间,放置在types里
import getScrollData from '../../utils/scroll'
// 这是获得滚动数据的js文件,具体怎样写的可以去我github开源项目里看
// 导出的是一个方法,这个方法返回一个对象
// {
// scrollTop: 0,
// scrollHeight: 0,
// windowHeight: 0,
// scrollBottom: 0
// }
const state = {
articleList: false,
// 布尔值,表示侧拉文章列表的开关
scroll: {
// 滚动相关的数据
scrollTop: 0,
scrollHeight: 0,
windowHeight: 0,
scrollBottom: 0
}
}
const getters = {
// articleList: ({status}) => status.articleList
// tips: 上面这种方法是错误的。这里的getters传入进来的不是state的根对象,
// 而是当前的state对象,在这里也就相当于是state = state.status
// 所以可以放心的用state
articleList: state => state.articleList
// 设置Getters,在.vue文件里可以使用mapGetters方法获得
}
const mutations = {
[types.ARTICLE_LIST] (state) {
state.articleList = !state.articleList
},
// 设置mutations,只有这个方法可以改变state
[types.SCROLLDATA] (state, scrollObj) {
state.scroll = scrollObj
}
}
const actions = {
articleListSwitch ({ commit }) {
commit(types.ARTICLE_LIST)
},
// 设置actions,可以在这里做一些异步的工作,然后使用commit来调用mutations修改state
pushScrollData ({ commit }) {
commit(types.SCROLLDATA, getScrollData(), { silent: true })
// 因为页面滚动触发频繁,会影响到我们平时的vuex的审查,所以这里选择静默提交
}
}
export default {
state,
getters,
actions,
mutations
}
// 而后的.vue文件就可以调用辅助方法达到获取状态或者控制状态的目的了

调试vuex最好使用vuejs的谷歌插件Devtools
或者去github下载源码自己编译
翻墙要梯子(vpn),想翻墙没vpn的我这里安利一个vpn,没什么其他,因为我正用的就是这个。
贴一个云梯推荐码http://referyt.com/?r=f688f08a26bf108d,通过推荐码注册可以优惠10元,笔者也可以得到10元优惠,在得到优惠的同时也是对我的鼓舞吧😂

渲染链接列表

接下来要做的就是做链接跳转。
切到AppArticleList.vue文件,也就是文章列表页。
大家可以在我的Demo里点击左上角的菜单按钮看到这个列表
!(AppArticleList.vue)[/img/vue/AppArticleList.jpg]

里面的内容是这样子的
(因为内容有些复杂,为了看的清晰点这是我提取出来的核心部分)
(源文件可以看https://github.com/leenty/vue2/blob/master/src/components/templates/AppArticleList.vue)

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<!-- 大致的内容就是渲染一个文章列表 -->
<template lang="pug">
.articleList(:class="{'articleList--Active': articleList}").l-page--Full
ul.articleList__content.u-ul--Reset
li(
v-for="(item, index) in list",
// vuejs的for循环,list指向了data里的list数组(看js部分)
:style="calcDelay(index)",
// 绑定返回一个css延时代码(在methods方法下)
// 主要功能是给菜单提供依次进入的动画效果
@click="articleListSwitch"
// 绑定点击事件,控制文章列表的出现和隐藏
).articleList__li
router-link(
// 通过list里的数据循环生成路由链接
:to="{name: item.name}",
// 绑定路由链接,这里用了{name: item.name}是因为相对来说,name比path方便
// item就是之前for循环里的item
active-class="articleList__link--Active"
// 之前的文章的第二篇里对路由时匹配的`linkActiveClass`做了配置,
// 这里用不到原来配置的class,所以可以用这个方法单独的做处理
// 把匹配到的链接的class改成了'articleList__link--Active'
).articleList__link.u-borderBox.u-link__inherit.l-flexV--c
.articleList__title
svg.svg__home.u-va--tb
use(xlink:href="#svg__articleIcon")
// 这里用的图标是svg的,关于这种用法,如果有兴趣我可以专门的介绍一下
| {{item.title}}
// 绑定链接的文字
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
// 导入vuex的两个将要用到的辅助方法
export default {
// export defalut是es6的模块导出方法,这里导出一个默认模块,即是一个vue对象
data () {
// 这是一种es6对象字面量的缩写,指向一个函数
// 扩展出来就是data: function data () {}
return {
// 而后在函数里去返回将要用到的数据
// 这么做返回的数据可以使用this访问到
list: [
{
name: 'vue2_1',
title: 'vue2.0一起在懵逼的海洋里越陷越深(一)'
},
{
name: 'vue2_2',
title: 'vue2.0一起在懵逼的海洋里越陷越深(二)'
},
{
name: 'vue2_3',
title: 'vue2.0一起在懵逼的海洋里越陷越深(三)'
},
{
name: 'vue2_4',
title: 'vue2.0一起在懵逼的海洋里越陷越深(四)'
},
{
name: 'vue2_5',
title: 'vue2.0一起在懵逼的海洋里越陷越深(五)'
}
]
}
},
computed: mapGetters(['articleList']),
// vue的计算方法对象,用法和data里的数据是一样的,
// 这里使用了mapGetters辅助函数,用来获得vuex的Getters里的articleList
// 这个方法会返回一个对象,所以如果没有其他计算方法的话直接赋值给computed就好了
// 有的话可以向下面这样:
// computed: {
// demo () {
// return this.list[0].name
// 在这里你可以使用this访问到data里的list
// },
// ...mapGetters(['articleList'])
// ...延展操作符,用于展开mapGetters得到的对象,使得与demo属性平级
// },
methods: {
// vue的方法对象,你可以在这里写你的事件处理函数或是其他方法
calcDelay: function (index) {
// 这是分配css动画延迟量的函数
return {
'transitionDelay': `.${index + 2}s`
}
},
...mapActions(['articleListSwitch'])
// 通过延展操作符,展开mapActions得到的actions对象
}
}
</script>
<style lang="stylus">
// css就不多说了
@import '../../assets/stylus/preinstall'
.articleList
background-color c-master
.articleList__content
width s-articleList
margin-top 8vh
max-height 84vh
color #fff
.articleList__li
transform translateX(-(s-articleList))
transition transform .3s cb-duang .2s
.articleList__link
width 100%
line-height 1.3em
padding-left 10px
min-height 50px
background-color rgba(255,255,255,.1)
&:hover
background-color rgba(255,255,255,.3)
svg
transform-origin bottom center
transform scale(.7)
.articleList__link--Active
background-color rgba(255,255,255,.4)
.articleList--Active .articleList__li
transform translateX(0)
.articleList__title
display block
width 100%
font-size 12px
&:first-line
font-size 14px
</style>

在写的过程中碰到了一个浏览器bug,这里还是说下
bug: 如果父元素进行了transform变换,那么子元素的position fixed将会失去作用,退化成position absolute
解决方案: 之前试过想要使用css的方式去绕过这个问题,但是发现不行,无奈使用了js去计算子元素退化成absolute后的差值,对其进行位移达到视觉的效果。
有兴趣的可以打开http://vue2.leenty.com/article/vue2_2然后审查元素,查看我的导航条也就是.header__content,这个时候你打开侧边栏然后上下滚动就会看到.header__content上js所不全的差值了

装载嵌套路由

打开article.vue文件,文件很简单,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template lang="pug">
.article
h1.l-ta--c 文章页面
.md-content
router-view
// 这个就是二级路由的装载点了
// 之前的`APP.vue`的`router-view`会装载当前模板,而后的文章都会装载到这个点上。
</template>
<script>
export default {
data () {
return {
}
}
}
</script>
<style lang="stylus">
</style>

关于触发滚动之后的一系列事件

关于触发滚动,考虑到之后可能会有多个页面需要使滚动方面的数据,所以就放在了vuex里作为状态的一部分。
所以滚动的监听就只需要监听一次就好了,多个地方的监听会浪费资源
最终,我把监听放在了App.vue里面,在里面监听’scroll’事件,然后放在vuex上,供有需要的组件去使用

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
<template lang="pug">
.app.u-clearfix
app-article-list
.app__content(
:class="{'app__content--Active': articleList}",
// 如果侧拉文章列表打开,那么添加这个class,相应的css效果将被触发
@scroll="pushScrollData"
// 绑定scroll事件
).l-page--Full
home-header
.app__bodyer.l-mH--auto
router-view
include ./assets/svg/all
</template>
<script>
import HomeHeader from './components/templates/HomeHeader.vue'
import AppArticleList from './components/templates/AppArticleList.vue'
import { mapGetters, mapActions } from 'vuex'
export default {
components: {
HomeHeader,
AppArticleList
},
computed: mapGetters([
'articleList'
]),
methods: {
...mapActions([
'pushScrollData',
'articleListSwitch'
])
}
}
</script>
<style lang="stylus">
@import './assets/stylus/main'
.app__menu
background-color c-master
.app__content
overflow auto
transition transform .5s
box-shadow 0 0 100px 5px rgba(0,0,0,0.3)
background-color c-bgc
transform-origin s-articleList center
.app__content--Active
transform scale(.9) translateX(s-articleList)
.app__bodyer
max-width max-width
background-color c-bgc
@media screen and (max-width: max-width)
.app__bodyer
padding 0 10px
</style>

ok,这么个流程走下来,算是把vuejs,vue-routervuex用在了一起

其他

演示地址(http://vue2.leenty.com)
源码地址(https://github.com/leenty/vue2)
github主页,觉得靠谱的话欢迎加星跟随