继续上一次实战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; }); }
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>
|
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(){ ... 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。因此我们可以像拼积木一样的把页面分割成多个块,然后可以自由地进行拆卸、组装,最后组成自己想展示的页面。这样做的好处就是多处展示,一处改动。因为用的组件是同一个。
举例说明:
发起投票可以分成文字、图片、定日子、看电影、去哪儿吃,
没有购买高级功能的用户,只能享受普通用户的特权。文字选项、定日子选项不能超过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
| 子组件: this.$dispatch('refreshQuestion'); 父组件: methods:{ getQuestion(){ } }, events:{ refreshQuestion(){ this.getQuestion(); } }
(没怎么用过,使用的场合很少)
|
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'), } ...
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。