Vue.js无疑是最近最火的一套前端框架,被设计为可以自底向上逐层应用。Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。其轻量级,渐进式的优点对开发者的吸引力毋庸置疑。本人玩过两年的Angular,对Vue也充满了好奇,于是也想认识一下Vue。
Vue.js安装
主流的安装方式当然是使用vue-cli工具,这跟angular挺像的。但是作为刚入门的开发者,官方比较推荐的安装方式是直接引入script。vue脚本也分为开发版vue.js和生产版vue.min.js,开发环境下推荐使用vue.js,可以在开发过程中看到常见错误相关的警告。你可以选择使用CDN服务,直接引入这个脚本即可进行开发。
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
本人还是习惯直接把脚本下载到项目文件夹中。
<script src="./lib/vue.js"></script>
学习Vue
学习Vue.js最好的方法当然是把代码都撸一遍,这样印象会比较深刻。
v-if
这个指令类似于angular的*ngIf,作为一个结构性指令,用来控制元素的插入和移除。当v-if=”true”时,元素正常显示;反之元素则被移除。
v-show
这个指令达到的效果看起来与v-if是一样的,但是从本质上来讲是不同的。v-show其实仅仅从样式上来控制显示和隐藏,改变的是display属性。与jQuery的show()和hide()方法有异曲同工之妙。
插值表达式
<span>{{ content }}</span>
这个是数据绑定的标准写法,与angular是一样的。content变量的值改变时,页面渲染也随之变化。插值表达式中支持js表达式。
v-html
从字面意思能看出来,该指令绑定的值会输出html内容,类似于jQuery的html()方法。在angular中好像没有看到这个指令,我是自定义了一个directive来实现的。
属性绑定v-bind
这个指令用来绑定元素属性,通常用于向子组件传递props。比如子组件studentList内部需要属性id来做逻辑判断,这个id可以由父组件来传递,这里就可以使用到v-bind。
<student-list v-bind:id="somevalue"></student-list>
v-bind:可以缩写为:
<student-list :id="somevalue"></student-list>
事件绑定v-on
用来绑定事件,比如鼠标点击事件,可以这样写:
<button v-on:click="toggleVueIf">Toggle VUE IF</button>
v-bind:可以缩写为@:
<button @click="toggleVueIf">Toggle VUE IF</button>
v-for
v-for用来遍历数组或对象
(1)数组,接收的参数上可以是两个,顺序是value, index
<ul v-for="(value, index) in vueForArray">
<li>{{ index }}: {{ value }}</li>
</ul>
(2)对象,接收的参数上可以是三个,顺序是value, key, index
<ul v-for="(value, key, index) in vueForObject">
<li>{{ index }}. {{ key }} : {{ value }}</li>
</ul>
计算属性computed
对于插值表达式中涉及到的复杂计算,建议用计算属性computed来替代。在new Vue时可以使用computed属性。
// html中
{{ reversedMessage }}
// vue实例中
computed: {
reversedMessage: function() {
return this.testMessage.split('').reverse().join('');
}
}
这里只是一个倒序的算法,不算很复杂,只是举例说明。
watch属性
我们可以使用watch来进行侦听,来响应数据的变化。上面计算属性的应用,我们也可以改用watch来写:
watch: {
testMessage: function() {
this.reversedMessage = this.testMessage.split('').reverse().join('');
}
}
这里,我们通过侦听testMessage的变化来做出响应,用于改变reversedMessage的值。
ps:那么computed和watch的应用场景怎么区分?
个人认为,computed更适合做一些数据值的计算,而涉及到侦听某变量需要做一些业务处理时,建议使用watch。
class绑定
<!-- 类似这种形式,与属性绑定大同小异。class的绑定值可以是键值对,也可以是数组。 -->
<div :class="{ category: isCategory, red: isRed }"></div>
style绑定
与class绑定类似,支持键值对语法(css键值对),也支持数组(多个样式对象同时作用)。
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
双向绑定v-model
这一特性与angular的[(ngModel)]是一致的。通常用于表单元素。
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
不过v-model还支持很多修饰符,如
(1).lazy
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
(2).number
自动将用户的输入值转为数值类型
(3).trim
自动过滤用户输入的首尾空白字符
.sync
看名字就知道了,同步的意思,如果绑定的属性加上这个修饰符,就实现了双向绑定。.sync破坏了单向数据流,于2.0 中移除,但又在 2.3.0 版本后以语法糖形式重新引入。
<comp :foo.sync="bar"></comp>
父子组件通信
父组件通过属性传递数据给子组件props,而子组件以this.$emit(eventname, data)的方式发事件给父组件,父组件模板中需要绑定对应事件。
// 父组件模板,通过绑定greet传递数据给子组件,@replay接收来自子组件的事件。
<child :greet="greetMsg" @reply="showReply"></child>
// 子组件props属性接收
props: ['greet']
// 子组件模板
<p>Greet from Father Component: {{greet}}</p>
<button @click="replyToFather">Reply</button>
// 点击button时调用replyToFather方法,通过$emit发送事件给父组件。
methods: {
replyToFather: function() {
this.$emit('reply', this.replyInfo);
}
}
slot
类似angular的ng-content。父组件可以将想要的内容插在子组件的插槽中。子组件slot标签通过name属性区分插槽,如name=”slot1”,不写name属性的就是默认插槽了;而父组件要在传递给插槽的标签中加上slot属性来区分插在哪个槽中,如slot=”slot1”,如果不加slot属性,则自动插在默认插槽中。slot标签中的内容都被视为备用内容,如果父组件不给子组件传入内容,则会显示子组件slot标签中的备用内容。使用如下:
<!-- 父组件模板 -->
<child>
<!-- 没有指定slot属性,所以插在子组件中没有name属性的slot位置 -->
<p>Message From Father</p>
<!-- 指定了slot属性的值为head,所以插在子组件中name属性值为head的slot位置 -->
<p slot="head">For Head</p>
<!-- 指定了slot属性的值为foot,所以插在子组件中name属性值为foot的slot位置 -->
<p slot="foot">For Foot</p>
<!-- 没有指定slot属性,所以插在子组件中没有name属性的slot位置 -->
<p>Other information...</p>
<child>
<!-- 子组件模板 -->
<div>
<slot name="head">head content...</slot>
<slot>Backup content...</slot>
<slot name="foot">foot content...</slot>
</div>
作用域插槽
官方提供的说明我不太看得懂,根据我个人的理解,一个很重要的应用场景就是用来抽象列表式组件。比如一个展示性的列表组件,但是具体展示图文,还是其他的,需要由其父组件来指定。我这里写了一个demo。
<!-- 父组件模板 -->
<my-list :items="movies">
<template slot-scope="props">
<li>
<img class="poster" :src="props.item.posterUrl"/>
<div>{{ props.item.moviename }}</div>
</li>
<!-- 我可以在这里调用各种组件,来适应各种需要,比如我可以做海报展示,那么我只要一个<img>;比如我只要电影名称的列表,那么就只要用到电影名称 -->
</template>
</my-list>
<!-- 子组件模板,模板中的items是由props接收的 -->
<ul>
<span class="hot-movie">近期热映</span>
<slot name="slot-item" v-for="item in items" :item="item"></slot>
</ul>
使用要点:
(1)父组件通过属性绑定将数据(一般是数组,毕竟是用于列表展示嘛)传递给子组件。
<my-list :items="movies"></my-list>
(2)父组件传递给子组件的内容用template标签包裹,template标签具备slot-scope=”props”,props来源于子组件slot标签绑定的属性集,而本例中slot标签绑定了item属性,所以template中的内容可以调用props.item下的属性。
动态组件
利用component标签及其is属性可以动态切换组件。curComponent的值为组件名。
<component :is="curComponent"></component>
keep-alive标签
利用keep-alive标签包裹component标签可以让切换组件保留在内存中,可以保留它的状态,避免重新渲染。
<keep-alive>
<component :is="curComponent"></component>
</keep-alive>
ref属性
ref属性实现了组件的引用,不过应当避免使用ref。
<ref-comp ref="refComp"></ref-comp>
<!-- 我们可以在父组件中直接改变子组件中的refValue属性 -->
this.$refs.refComp.refValue += 'a';
transition过渡
在组件外包裹一层transition标签,name标识transition,即可为组件加上过渡效果。如:
<transition name="fade">
<p v-show="showFade">Fade</p>
</transition>
在进入/离开的过渡中,会有 6 个 class 切换,默认 class 名称格式如下。
- fade-enter // 进入过渡开始,一般在这个class下定义-过渡开始时的css状态
- fade-enter-active // 进入过渡活跃状态,一般在这个-class下定义进入过渡过程中的动画
- fade-enter-to // 进入过渡结束,一般在这个class下定义过渡结束时的css状态。ps:离开过渡不再说明,类似开始过渡。
- fade-leave // 离开过渡开始
- fade-leave-active // 离开过渡活跃状态
- fade-leave-to // 离开过渡结束
我们也可以自定义过渡类名,需要增加transition标签的属性,这样就可以使用第三方的动画库了。用法如下:
<transition name="fade" enter-active-class="animated tada" leave-active-class="animated bounceOutRight">
<p v-show="showFade">Fade</p>
</transition>
过渡效果主要的应用场景有:
- v-if
- v-show
- 动态组件
transition有着一系列javascript钩子函数,从字面意思可以与过渡的class对应起来,如下所示:
<transition
:css="false"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
>
......
</transition>
钩子函数都有参数el,用于引用添加了transition包裹的元素。enter和leave钩子函数中额外有一个参数done,done是一个回调函数,是必须在最后调用的。否则,它们会被同步调用,过渡会立即完成。
enter: function (el, done) {
// ...
done();
}
有了js钩子函数,那么我就可以不使用class来定义过渡的效果了,我可以使用js动画库,如velocity.js,这个时候最好添加v-bind:css=”false”,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
我们可以给元素设置初始渲染的过渡效果。
<transition appear>
<!-- ... -->
</transition>
appear拥有相同的class钩子和js钩子,也可以自定义类名。
多个元素的过渡
transition中多个元素过渡时,可以这样写,给每个元素加上key属性来作为唯一标识:
<transition>
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>
上述代码也可以简写为绑定key的形式,而不再需要if,else:
<transition>
<button :key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>
其中的isEditing ? ‘Save’ : ‘Edit’也可以抽象为一个computed属性。
除了多个元素的过渡,类似的还有动态组件的过渡。
<transition name="fade-dynamic" mode="out-in">
<component :is="curComponent"></component>
</transition>
mode属性定义了过渡模式,防止当前元素的离开与下一个元素的进入产生重叠现象:
- in-out:新元素先进行过渡,完成之后当前元素过渡离开。
- out-in:当前元素先进行过渡,完成之后新元素过渡进入。
列表过渡
列表过渡用到的组件是transition-group。transition-group会以真实的元素存在于html文档中,而transition是不存在于html文档中的。transition-group存在的形式由tag属性指定,如
<transition-group name="group-list" tag="div">
<div v-for="item in groups" :key="item.id" class="group-item">
<img class="group-item-img" :src="item.picUrl"/>
</div>
</transition-group>
为了让group-list的过渡效果平滑,有一个v-move属性,也是通过绑定class实现
.group-list-move {
transition: transform 1s;
}
另一种实现方式如下:
.group-item {
margin: 10px;
display: inline-block;
width: 100px;
height: 60px;
transition: all 1s;
}
.group-item-img {
width: 100%;
height: 100%;
}
/* 这个作用是让元素在离开的过程中不占位,使过渡看起来十分平滑 */
.group-list-leave-active {
position: absolute;
}
/* .group-list-enter-active, .group-list-leave-active {
transition: all 1s;
} */
.group-list-enter, .group-list-leave-to {
opacity: 0;
transform: translateY(30px);
}
/* move属性作用于元素的改变定位的过程中,这里会让transform有一个过渡效果;
如果需要让列表整体有一个整体性过渡,那么可以在group-item类中加上transition属性,
那么也就不需要group-list-enter-active和group-list-leave-active类中有transition属性了。
*/
/* .group-list-move {
transition: transform 1s;
} */
mixin
mixin是一种提高编写效率的写法,详情请参考mixin。
vue自定义指令
一个指令定义对象可以提供如下几个钩子函数。
- bind:只调用一次,指令第一次绑定到元素时调用。
- inserted:被绑定元素插入父节点时调用
- update
- componentUpdated
- unbind
目前未对指令进行深究,只写了一个简单的指令。
directives: {
autoClick: {
inserted: function(el) {
el.click();
}
}
}
vue组件生命周期
参考:
(1)Vue2.0 探索之路——生命周期和钩子函数的一些理解
(2)API