STAY HUNGRY , STAY FOOLISH.

求知若饥,虚心若愚。

       浏览:

实战Vue(二)

继续上一次实战Vue的讲解。回顾上篇,主要讲解的内容有使用Vue的准备工作、环境的搭建及分析Vue与JQuery的区别。
本章将进一步讲解Vue,我会从Vue作者尤雨溪去年在携程C4技术分享沙龙公开演讲的话语中进行剖析和讲解Vue的语法和思想。


3.2 剖析Vue相关的语句及其语法

1.应用逻辑状态本身真相只存在一个地方
剖析:要想理解整句话的含义,需要理解该句话的关键词,“应用逻辑状态”。
什么是应用逻辑状态?我认为应用逻辑状态是指改变页面的数据
该数据包括ajax传递的参数和返回的参数及前端js的参数。说的有点抽象,举例:
a.ajax传递的参数
添数据———->发起投票页面:
默认是空的页面,什么都没有填写
发起投票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data(){
return{
params:{
openID:$.basis.PATH.user.openID||'',
type:0,//文字投票
picTemplateID:1000001,//默认单选
title:'',
item:['',''],
item_type:0,
descimage:'',
desc:'',
endType:0,
max_choose:1,
isOpenAnswer:3,
open_to_creater:1
}
}
}

填写好数据之后的页面:
发起投票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data(){
return{
params:{
openID: "oI86Dt_IgGPfsdsXm4cVOKp-9lXQ",
type: 0,
picTemplateID: 1000001,
title: "投票标题",
item: [
"选项1",
"选项2"
],
item_type: 1,
descimage: "base64格式的链接",
desc: "关于此投票,说点什么吧",
endType: 1,
max_choose: 1,
isOpenAnswer: 3,
open_to_creater: 1
}
}
}

和以前JQuery相比,这样的好处是应用状态本身只存在params这一个地方,用户进行文本输入和图片上传时,我们不需要挨个去获取DOM的值然后放在params里面,这些过程在Vue的数据绑定中已经动态实现,用户操作什么,params里面的值就自动改变成什么。


b.ajax返回的参数
查数据———->投票详情页面:
去哪儿吃:
去哪儿吃


普通多选

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
data(){
return{
question:''
}
}
getQuestion(){
var self=this;
$.get(url,(rst)=>{
self.question=rst;
});
}
//通过调用getQuestion方法获取的值
//发起人参与公开去哪儿吃投票
question:{
"type": 2,
"isone":0,
"is_open_answer":3,
...
]
//参与者参与匿名普通多选投票
question:{
"type": 0,
"isone":1,
"is_open_answer":2,
...
]

前端通过ajax获取值后会根据返回的值去判断该将怎样的页面展示给用户。每个页面最终只会展示一种效果,要么是场景类投票的效果,要么是普通类投票的效果,因此和上述所说的应用逻辑状态本身只存在一个地方是一致的。


c.前端js的参数
加载中:
加载中
给予提示:
给予提示

1
2
3
4
5
6
<style>
.hide{
display:none;
}
</style>
<div class='wait hide'></div>

JQuery:

1
2
3
4
$('.wait').removeClass('hide');
$.post(url,params,()=>{
$('.wait').addClass('hide');
});

Vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data(){
return{
waitParams:{
waitShow:false
},
tipParams:{
tipShow:false,
tipInfo:'',
isClick:false
}
}
}
var self=this;
this.waitParams.waitShow=true;
$.post(url,params,()=>{
self.waitParams.waitShow=false;
});

当前端调用接口之前会出现加载中及蒙层,等接口成功返回东西后将其隐藏;还有用户操作不当的时候,需提示其信息。以前JQuery的思路是先获取DOM,然后使用removeClass(‘hide’)将其显示,等成功后addClass(‘hide’)。尽管也很简单,但是没有与数据关联起来,而Vue实现了。

一句话概括,数据改变页面,数据只在一个地方。

2.传统前端数据状态放在DOM表单,应用状态存在多个地方
这句话的意思可直接看代码解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form method="post" action="/url" enctype="multipart/form-data">
<div class="content">
<input type="hidden" class="openID" name="openID">
<input type="hidden" class="createKey" name="createKey">
<div class="q-input">
<span class="hook"><i></i></span>
<input name="title" class="q-title" type="text" placeholder="(限20个字内)" maxlength="20">
</div>
<input type="hidden" class="endType" name="endType">
<input type="hidden" class="open_to_creater" name="open_to_creater" value="1" />
<input type="hidden" class="isRepeatSingle" name="isRepeatSingle" value="1" />
<input type="hidden" class="isOpenAnswer" name="isOpenAnswer" value="3" />
<input type="hidden" class="picTemplateID" name="picTemplateID">
</div>
</form>
<!--应用状态存在多个input里面 -->

3.DOM只是Model自然的映射,不碰DOM,碰Model
Vue的核心思想就是想让前端尽量少用或不用JQuery、Zepto,这样的好处是不需要每次去做重复的事情(获取DOM,改变DOM的值或样式)。使用Vue后,只需要将DOM和Model之间建立好联系,直接碰Model就好。

4.data就是Model,el就是View,vm就是ViewModel
官网上的Demo:

1
2
3
4
5
6
7
<div id="app">{{ message }}</div>
vm =new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
})

一个完整的vue文件包含以下内容:
data就是Model,template就是View,整个export就是ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<style>
.hello{ font-size:24px }
</style>
<template>
<div class='hello'>{{ message }}</div>
</template>
<script>
export default{
data(){
return{
message:'Hello Vue.js!'
}
}
}
</script>

5.指令本质就是封装DOM操作,每个指令观察一片数据以及管理对应DOM元素,每当观察数据产生变化的时候,它就对应更新其管理的那块DOM
要想理解这句话,请看下面的图片:
指令
DOM监听器:如点击事件click、滚动事件srcoll、输入值改变事件change
指令:如v-if、v-show、v-for、v-else、v-model、v-lazy、v-tap(后面会有详细介绍)
以v-lazy为例,该指令目的是实现图片的懒加载、加载后以base64方式展现及让图片居中,等用户滚动后,慢慢将其图片加载出来,减少初始资源的请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Vue.directive('lazy',{
bind:function(){
// 准备工作,如注册事件
window.addEventListener('scroll',function(){});
},
update:function(){
//值更新时的工作
//懒加载、base64转换、图片居中等逻辑操作
...
this.el.setAttribute('src', this.src);
...
},
unbind:fucntion(){
//清理工作,如移除事件
window.removeEventListener('scroll',function(){});
}
});

use:
<img v-lazy='url1'>
<img v-lazy='url2'>

当使用v-lazy这个指令后,指令会分别观察url1和url2的img元素,当用户滚动时,会将url1和url2传入src属性后,随后图片将加载到各自的img上,最后展现给用户。

6.DOM里添加listeners,事件修改Model,不修改DOM
这个是告诉初学者在使用Vue语法的时候要注意的事项。
例如实现点击按钮弹出一个页面

1
2
3
4
5
<page v-show='isShow.page'></page>
<button @click='openPage()'>按钮</button>
openPage(){
this.isShow.page=true;
}

7.每一个组件都可以看做是一个ViewModel,所以可以把页面抽象为ViewModel Tree
看下面的图:
viewmodel
一个页面可以由多个组件构成,每个组件就是一个viewmodel。因此我们可以像拼积木一样的把页面分割成多个块,然后可以自由地进行拆卸、组装,最后组成自己想展示的页面。这样做的好处就是多处展示,一处改动。因为用的组件是同一个。
举例说明:
发起投票可以分成文字、图片、定日子、看电影、去哪儿吃,
没有购买高级功能的用户,只能享受普通用户的特权。文字选项、定日子选项不能超过20个、图片选项、看电影选项、去哪儿吃选项不能超过9个,因此需要根据不同投票类型去展示不同的东西,这时可以封装成同一个组件VoteTipInfo。
文字投票


图片投票

代码

上面的代码只是提示组件,整个发起页面从上到下分成提示组件、选项组件、背景故事组件、模板组件、时间组件等。


3.3重点语法的讲解及生命周期(适合理解和学习过vue的)

1.实现父子组件之间的数据传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 父组件-->
<vote-category :user-info='user' :number.sync='number'></vote-category>
data(){
return{
/**父组件参数**/
user:{},
number:1
}
}
components:{
'vote-category':require('../components/ui/home/voteCategory.vue')
}

<!-- 子组件使用-->
{{ userInfo }}

props:['userInfo']

注意事项:如果传递的不是json对象,请使用sync实现双向绑定。

2.vue-router实现组件与组件之间的数据传递:
方案一:

1
this.$route.router.go({path:'/lucky/list?openID='+openID+'&questionID='+questionID});

方案二:
Vuex
该方案适合数据传递有很多值的情况,如json对象等。优点是全局变量,缺点是学习难点稍大,重点理解下modules、store、action这三个概念,自己使用一次就会了。

3.组件之间可以通过事件系统进行通讯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//子组件->父组件 $dispatch()
子组件:
this.$dispatch('refreshQuestion');
父组件:
methods:{
getQuestion(){
//最终执行的代码
}
},
events:{
refreshQuestion(){
this.getQuestion();
}
}
//父组件->子组件 $boardcast()
// return false阻断传递
(没怎么用过,使用的场合很少)

4.track-by、$index的讲解
track-by在v-for指令上使用能复用vm和DOM元素,提供渲染效率。$index即当json格式的数组里面没有唯一标识符时使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//有唯一标识符时:
<ul class="img-list">
<li class="img-item" v-for='item in choiceList' track-by='business_id'>
<img class="img" :src="item.s_photo_url">
</li>
<ul/>

//无唯一标识符时:
<ul>
<li v-for='item in list' track-by='$index'>
<div class="header-img">
<img v-lazy='item.HEADIMGURL'>
</div>
</li>
</ul>

5.v-clock的讲解
v-clock这个指令可以隐藏未编译的Mustache标签直到实例准备完毕。
简单讲就是括号里面的值真正编译成功后才展示到页面上,页面上不会出现形式的字符串,只会出现赋的值。

1
2
3
4
5
6
7
8
<style>
[v-cloak] {
display: none;
}
</style>
<div @click='createVote' v-cloak>
我也要发起{{ createVoteInfo }}
</div>

6.:style和:class的妙用
该使用可以对相同页面的不同风格进行随意改变,如差不多的页面有相同的按钮,只是颜色稍有点不同,如去哪儿吃页面要显示红色文字、看电影页面要显示蓝色文字,那么可以这样使用。

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
<!-- style用法 -->
<ul class="options">
<li v-for='item in question.item' track-by='item_index'>
<div class="progress">
<div :data-value="item.percentage" :style='{width:"0%",background: colors[$index] }'></div>
</div>
</li>
</ul>

<!-- class用法 -->
<span
:class='["cardType",selectClass.titleClass]' v-cloak>
{{ selectClass.titleName }}
</span>

data(){
return{
selectClass:{},
voteClass:{
eat:{
titleName:'去哪儿吃',
titleClass:'eat',
color:'#A33B34'
},
moive:{
titleName:'看电影',
titleClass:'moive',
color:'#6066FC'
}
}
}
},
ready(){
if(type == 2){
this.selectClass=this.voteClass.eat;
}else if(type == 3){
this.selectClass=this.voteClass.moive;
}
}

7.slot的讲解
slot翻译过来为插槽,顾名思义使用slot可以插些组件到一个页面。简单理解就是子类使用slot后,父类定义的各种组件可以插入子类所设定的模板中。
如:
投票详情子类detailVote.vue里面定义模板:
子类

投票详情父类detailVoteBasis.vue里面插入组件:
父类

8.component is的讲解
上面使用slot的时候有个弊端就是一个页面的一个插槽只能对应一个组件,如果一个页面的一个插槽想对应多个组件的话,就需使用component is。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<component  :is='currentVoteCardView'></component>
data(){
return{
currentVoteCardView:'vote-card-defaul'
}
},
components:{
'vote-card-default':require('../../components/ui/detailVote/voteCard/basis/voteCardDefault.vue'),//默认投票
'vote-card-text':require('../../components/ui/detailVote/voteCard/basis/voteCardText.vue'),//文字投票(单选、多选)
'vote-card-single-image':require('../../components/ui/detailVote/voteCard/basis/voteCardSingleImage.vue'),//单图投票(赞与不赞、对与错、星级评比)
'vote-card-multiple-image':require('../../components/ui/detailVote/voteCard/basis/voteCardMultipleImage.vue'),//多图投票(单选、多选、排序、PK)
}
...
//根据投票picture_template_id确定对应的模块
var templateId=result['picture_template_id'];
if(templateId == 1000001 || templateId == 1000002){//文字投票
self.currentVoteCardView='vote-card-text';
}else if(templateId == 1 || templateId == 3 || templateId == 1002001){//单图投票
self.currentVoteCardView='vote-card-single-image';
}else if(templateId == 31 || templateId == 32 || templateId == 33 || templateId == 34){//多图投票
self.currentVoteCardView='vote-card-multiple-image';
}

9.v-text、v-if、v-show、v-else、v-for、v-model、v-lazy、v-tap各种指令的适用场景。
v-text适用场景是需要根据不同的状态展示不同的值的时候。

1
<span v-text='question.status == 1 ? "进行中" : "已关闭"'></span>

v-if适用场景是第一次显隐或不需要显隐切换时使用,因为它为false的时候不会生成DOM。

1
2
<span v-if='isOpenClass =="open"'>我发狠话啦,参与投票的小伙伴必须公开数据!</span>
<span v-else>小伙伴们放心投,此投票已设为匿名投票。</span>

二选一让它第一次显示哪个的时候可以使用v-if,例子里面只会生成一个span标签。


v-show适用场景是经常显隐的切换,因为它无论什么时候都会生成DOM。

1
2
<span v-show='isOpenClass =="open"'>我发狠话啦,参与投票的小伙伴必须公开数据!</span>
<span v-else>小伙伴们放心投,此投票已设为匿名投票。</span>

例子里面只会生成两个span标签。
v-else就是其他的意思,和v-if或v-show一同使用。


v-for适用场景是遍历ajax返回的json格式的数组展示其数据,如列表等。

1
2
3
4
5
<ul class="img-list">
<li class="img-item" v-for='item in choiceList' track-by='item_index' v-cloak>
<img class="img hide" :src="item.item_image_url">
</li>
</ul>

v-model适用场景是用户进行输入时,input的值会跟着其改变。

1
2
3
4
<textarea class="reply-text" rows="10" cols="10" placeholder="回复 {{ commentWho.nikename }}:"
v-model='commentWho.content'></textarea>

<input placeholder="填写个数" type='number' v-model='price.total'>

v-lazy适用场景是图片懒加载。

1
2
3
<div class="header-img">
<img v-lazy='item.HEADIMGURL'>
</div>

v-tap适用场景是想阻止200ms的延迟点击,想快速点击。

1
<div class="add-option" v-tap='addItem()' v-show='isAddOpen'></div>

10.页面的生命周期
生命周期

重点理解生命周期的created、ready、attached、watch、computed。