本文前言
本笔记建立在书籍《Vue.js实战 / 梁灏编著》的基础上,旨在帮助有 Vue.js 基础的朋友快速回忆 Vue.js 的细碎内容。初学者建议阅读《Vue.js实战》全书进行系统学习。
Model-View-View-Model 的缩写,由经典的软件架构 MVC 衍生来。当 View(视图层)变化时,会自动更新到 ViewModel(视图模型),反之亦然。View 和 ViewModel 之间通过双向绑定(data-binding)建立联系。
<script src="https://unpkg.com/vue/dist/vue.min.js">script>
<div id="app">
<h1>你好,{{name}}h1>
div>
<script>
var app = new Vue({
el: '#app',
data: {
name: '世界'
}
})
script>
下面的引用是对创建 Vue.js 应用的补充说明,便于深入理解。
Vue.js 应用的创建很简单,通过构造函数 Vue 就可以创建一个 Vue 的根实例,并启动 Vue 应用:
var app = new Vue({ //选项 })
变量 app 就代表了这个 Vue 实例。事实上,几乎所有的代码都是一个对象,写入 Vue 实例的选项内的。
首先,必不可少的一个选项就是 el。el 用于指定一个页面中已存在的 DOM 元素来挂载 Vue 实例,它可以是 HTMLElement,也可以是 CSS 选择器,比如:
<div id="app">div> <script> var app = new Vue({ el: document.getElementById('app') //或者是'#app' }) script>
v-bind 的基本用途是动态更新 HTML 元素上的属性,比如 id,class 等,例如下面几个示例:
<div id="app">
<a v-bind:href="url">链接a>
<img v-bind:src="imgUrl">
div>
<script>
var app = new Vue({
el: '#app',
data: {
url: 'https://www.github.com',
imgUrl: 'http://xxx.xxx.xx/img.png'
}
})
script>
另一个非常重要的指令就是 v-on,它用来绑定事件监听器,这样我们就可以做一些交互了,先来看下面的示例:
<div id="app">
<p v-if="show">这是一段文本p>
<button v-on:click="handleClose">点击隐藏button>
div>
<script>
var app = new Vue({
el: '#app',
data: {
show: true
},
methods: {
handleClose: function() {
this.show = false;
}
}
})
script>
语法糖是指在不影响功能的情况下,添加某种方法实现同样的效果,从而方便程序开发。
Vue.js 的 v-bind 和 v-on 指令都提供了语法糖,也可以说是缩写,比如 v-bind,可以省略v-bind,直接写一个冒号“: ”,v-on 可以直接用“@”来缩写。
在模板中双向绑定一些数据或表达式的时候,如果表达式过长,或逻辑更为复杂时,就会变得臃肿甚至难以阅读和维护,比如:
<div>
{{ text.split(',').reverse().join(',') }}
div>
这里的表达式包含3个操作,并不是很清晰,所以在遇到复杂的逻辑时应该使用计算属性。上例可以用计算属性进行改写:
<div id="app">
{{ reversedText }}
div>
<script>
var app = new Vue({
el: '#app',
data: {
text: '123,456'
},
computed:{
reversedText: function(){
return this.text.split(',').reverse().join(',');
}
}
})
script>
<div id="app">
姓名:{{ fullName }}
div>
<script>
var app = new Vue({
el: '#app',
data: {
firstName: 'Jack',
lastName: 'Green'
},
computed: {
fullName: {
//getter, 用于读取
get: function () {
return this.firstName + '' + this.lastName;
},
//setter, 写入时触发
set: function (newValue) {
var names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[names.length - 1];
}
}
}
})
script>
当执行 app.fullName = 'John Doe';
时,setter 就会被调用,数据 firstName 和 lastName 都会相对更新,视图同样也会更新。
计算属性还有两个很实用的小技巧容易被忽略:一是计算属性可以依赖其他计算属性;二是计算属性不仅可以依赖当前Vue实例的数据,还可以依赖其他实例的数据,例如:
<div id="app1">div>
<div id="app2">
{{ reversedText }}
div>
<script>
var app1 = new Vue({
el: '#app1',
data: {
text: '123,456'
}
});
var app2 = new Vue({
el: '#app2',
computed: {
reversedText: function(){
// 这里以来的是实例 app1 的数据 text
return app1.text.split(',').reverse().join(',');
}
}
})
script>
计算属性是基于它的依赖缓存的。一个计算属性所依赖的数据发生变化时,它才会重新取值,所以上述例子中的 text 只要不改变,计算属性也就不更新,例如:
computed: {
now: function () {
return Date.now();
}
}
这里的 Date.now()
不是响应式依赖,所以计算属性 now 不会更新。但是 methods 则不同,只要重新渲染,它就会被调用,因此函数也会被执行。
使用计算属性还是 methods 取决于你是否需要缓存,当遍历大数组和做大量计算时,应当使用计算属性,除非你不希望得到缓存。
在数据绑定中,最常见的两个需求就是元素的样式名称 class 和内联样式 style 的动态绑定,它们也是 HTML 的属性,因此可以使用 v-bind 指令。我们只需要用 v-bind 计算出表达式最终的字符串就可以,不过有时候表达式的逻辑较复杂,使用字符串拼接方法较难阅读和维护,所以 Vue.js 增强了对 class 和 style 的绑定。
给 v-bind:class 设置一个对象,可以动态地切换 class,例如
<div id="app">
<div :class="{'active': isActive}">div>
div>
<script>
var app = new Vue({
el: '#app',
data: {
isActive: true
}
})
script>
上面示例中,类名 active 依赖于数据 isActive,当其为 true 时,div 会拥有类名 Active,为 false 时则没有,所以上例最终渲染完的结果是:
<div class="active">div>
对象中也可以传入多个属性,来动态切换 class 。另外,:class 可以与普通 class 共存,例如:
<div id="app">
div>
当:class 的表达式过长或逻辑复杂时,还可以绑定一个计算属性,这是一种很友好和常见的用法,一般当条件多于两个时,都可以使用data或computed,例如使用计算属性:
<div id="app">
<div :class="classes">div>
div>
<script>
var app = new Vue({
el: '#app',
data: {
isActive: true,
error: null
},
computed: {
classes: function(){
return {
active: this.isActive && !this.error,
'text-fail': this.error && this.error.type === 'fail'
}
}
}
})
script>
数组语法
当需要应用多个class时,可以使用数组语法,给:class绑定一个数组,应用一个class列表:
<div id="app">
<div :class="[activeClass, errorClass]">div>
div>
<script>
var app = new Vue({
el: '#app',
data: {
activeClass: 'active',
errorClass: 'error'
}
})
script>
渲染后的结果为:
<div class="active error">div>
还可以在数组语法中使用对象语法:
<div id="app">
<div :class="[{'active': isActive}, errorClass]">div>
div>
<script>
var app = new Vue({
el:'#app',
data: {
isActive: true,
errorClass: 'error'
}
})
script>
当然,与对象语法一样,也可以使用data,computed 和 methods 三种方法,以计算属性为例:
<div id="app">
<button :class="classes">button>
div>
<script>
var app = new Vue({
el: '#app',
data: {
size: 'large',
disabled: true
},
computed: {
classes: function(){
'btn',
{
['btn-'+this.size]: this.size != '',
['btn-disabled']: this.disabled
}
}
}
})
script>
示例中的样式 btn 会始终应用,当数据 size 不为空时,会应用样式前缀 btn-,后加 size 的值;当数据 disabled 为真时,会应用样式 btn-disabled,所以该示例最终渲染的结果为:
<button class="btn btn-large btn-disabled">button>
使用计算属性给元素动态设置类名,在业务中经常用到,尤其是在写复用的组件时,所以在开发过程中,如果表达式较长或逻辑复杂,应该尽可能地优先使用计算属性。
绑定内联样式
使用 v-bind:style
(即 :style
)可以给元素绑定内联样式,方法与 :class 类似,也有对象语法和数组语法,看起来很像直接在元素上写 CSS。
CSS属性名称使用驼峰命名(camelCase)或短横分隔命名(kebab-case)。
大多数情况下,直接写一长串的样式不便于阅读和维护,所以一般写在data或computed里,以data为例改写上面的示例:
<div id="app">
<div :style="styles">文本div>
div>
<script>
var app = new Vue({
el: '#app',
data: {
styles: {
color: 'red',
fontSize: 14 + 'px'
}
}
})
script>
应用多个样式对象时,可以使用数组语法:
<div :style="[styleA, styleB]">文本div>
在实际业务中,:style 的数组语法并不常用,因为往往可以写在一个对象里面;而较为常用的应当是计算属性。
另外,使用 :style时,Vue.js 会自动给特殊的 CSS 属性名称增加前缀,比如 transform
第5章 内置指令
基本指令
v-cloak
v-cloak 不需要表达式,它会在 Vue 实例结束编译时从绑定的HTML元素上移除,经常和 CSS 的display: none;
配合使用。
在一般情况下,v-cloak 是一个解决初始化慢导致页面闪动的最佳实践,对于简单的项目很实用,但是在具有工程化的项目里,比如 webpack 和 vue-router,项目的HTML结构只有一个空的div元素,剩余的内容都是由路由去挂载不同组件完成的,所以不再需要v-cloak.
v-once
v-once 也是一个不需要表达式的指令,作用是定义它的元素或组件只渲染一次,包括元素或组件的所有子节点。首次渲染后,不再随数据的变化重新渲染,将被视为静态内容。
v-once 在业务中也很少使用,当你需要进一步优化性能时,可能会用到。
条件渲染指令
v-if、v-else-if、v-else
与JavaScript的条件语句 if,else,else if 类似,Vue.js 的条件指令可以根据表达式的值在DOM中渲染或销毁元素/组件。
<div id="app">
<p v-if="status === 1">当status为1时显示该行p>
<p v-else-if="status === 2">当status为2时显示该行p>
<p v-else>否则显示该行p>
div>
<script>
var app = new Vue({
el: '#app',
data: {
status: 1
}
})
script>
v-show
v-show 的用法与 v-if 基本一致,只不过 v-show 是改变元素的 CSS 属性 display。当 v-show 表达式的值为false时,元素会隐藏,查看DOM结构会看到元素上加载了内联样式 display: none;
。
<div id="app">
<p v-show="status === 1">当status为1时显示该行p>
div>
<script>
var app = new Vue({
el: '#app',
data: {
status: 2
}
})
script>
渲染后的结果为:
<p style="display: none;">当status为1时显示该行p>
列表渲染指令 v-for
当需要将一个数组遍历或枚举一个对象循环显示时,就会用到列表渲染指令 v-for。它的表达式需结合 in 来使用,类似 item in items
的形式,看下面的示例:
<div id="app">
<ul>
<li v-for="book in books">{{ book.name }}li>
ul>
div>
<script>
var app = new Vue({
el: '#app',
data: {
books: [
{ name: '《Vue.js 实战》'},
{ name: '《JavaScript 语言精粹》' },
{ name: '《JavaScript 高级程序设计》'}
]
}
})
script>
渲染结果:
-
《Vue.js 实战》
-
《JavaScript 语言精粹》
-
《JavaScript 高级程序设计》
v-for 的表达式支持一个可选参数作为当前项的索引,例如:
<div id="app">
<ul>
<li v-for="(book, index) in books">{{ index }} - {{ book.name }}li>
ul>
div>
<script>
//内容省略
script>
渲染结果:
-
0 - 《Vue.js 实战》
-
1 - 《JavaScript 语言精粹》
-
2 - 《JavaScript 高级程序设计》
除了数组外,对象的属性也是可以遍历的,例如:
<div id="app">
<span v-for="value in user">{{ value }}span>
div>
<script>
var app = new Vue({
el: '#app',
data: {
user: {
name: 'Aresn',
gender: '男',
age: 26
}
}
})
script>
渲染结果:
Aresn 男 26
遍历对象属性时,有两个可选参数,分别是键名和索引:
<div id="app">
<ul>
<li v-for="(value, key, index) in user">
{{index}} - {{key}}: {{value}}
li>
ul>
div>
<script>
var app = new Vue({
el: '#app',
data: {
user: {
name: 'Aresn',
gender: '男',
age: 26
}
}
})
script>
渲染后的结果:
-
0 - name: Aresn
-
1 - gender: 男
-
2 - age: 26
第6章 表单与 v-model
表单控件在实际业务较为常见,比如单选、多选、下拉选择、输入框等,用它们可以完成数据的录入、校验、提交等。Vue.js 提供了 v-model 指令,用于在表单类元素上双向绑定数据,例如在输入框上使用时,输入的内容会实时映射到绑定的数据上。例如下面的例子:
<div id="app">
<input type="text" v-model="message" placeholder="输入...">
<p>输入的内容是:{{message}}p>
div>
<script>
var app = new Vue({
el: '#app',
data: {
message: ''
}
})
script>
在输入框输入的同时,{{message}} 也会实时将内容渲染在视图中。
单选按钮:
单选按钮在单独使用时,不需要 v-model,直接使用 v-bind 绑定一个布尔类型的值,为真时选中,为否时不选,例如:
<div id="app">
<input type="radio" :check="picked">
<label>单选按钮label>
div>
<script>
var app = new Vue({
el: '#app',
data: {
picked: true
}
})
script>
如果是组合使用来实现互斥选择的效果,就需要 v-model 配合 value 来使用:
<div id="app">
<input type="radio" v-model="picked" value="html" id="html">
<label for="html">HTMLlabel>
<br>
<input type="radio" v-model="picked" value="js" id="js">
<label for="html">JavaScriptlabel>
<br>
<input type="radio" v-model="picked" value="css" id="css">
<label for="html">CSSlabel>
<br>
<p>选择的项是:{{picked}}p>
div>
<script>
var app = new Vue({
el: '#app',
data: {
picked: 'js'
}
})
script>
复选框:
复选框也分单独使用和组合使用,不过用法稍与单选不同。复选框单独使用时,也是用 v-model 来绑定一个布尔值,例如:
<div id="app">
<input type="checkbox" v-model="checked" id="checked">
<label for="checked">选择状态:{{checked}}label>
div>
<script>
var app = new Vue({
el: '#app',
data: {
checked: false
}
})
script>
在勾选时,数据 checked 的值变为了 true,label 中渲染的内容也会更新。
组合使用时,也是 v-model 与 value 一起,多个勾选框都绑定到同一个数组类型的数据,value 的值在数组当中,就会选中这一项。这一过程也是双向的,在勾选时,value 的值也会自动 push 到这个数组中,示例代码如下:
<div id="app">
<input type="checkbox" v-model="checked" value"html" id="html">
<label for="html">HTMLlabel>
<br>
<input type="checkbox" v-model="checked" value"js" id="js">
<label for="html">JavaScriptlabel>
<br>
<input type="checkbox" v-model="checked" value"css" id="css">
<label for="html">CSSlabel>
<br>
div>
<script>
var app = new Vue({
el: '#app',
data: {
checked: ['html', 'css']
}
})
script>
第7章 组件详解
7.1 组件与复用
组件需要注册后才可以使用。注册有全局注册和局部注册两种方式
全局注册示例代码如下:
<div id="app">
<my-component>my-component>
div>
<script>
Vue.component('my-component', {
template: '这里是组件的内容'
});
var app = new Vue({
el: '#app'
})
script>
局部注册示例代码如下:
<div id="app">
<my-component>my-component>
div>
<script>
var Child = {
template: '局部注册组件的内容'
};
var app = new Vue({
el: '#app',
components: {
'my-component': Child
}
})
script>
组件中 data 的使用:
组件在使用data时,和实例稍有区别,data必须是函数,然后将数据return出去,例如:
<div id="app">
<my-component>my-component>
div>
<script>
Vue.component('my-component', {
template: '{{message}}',
data: function(){
return {
message: '组件内容'
}
}
});
var app = new Vue({
el: '#app'
})
script>
复用组件时的数据隔离
JavaScript 对象是引用关系,所以如果 return 出的对象引用了外部的一个对象,那这个对象就是共享的,任何一方修改都会同步。比如下面的示例:
<div id="app">
<my-component>my-component>
<my-component>my-component>
<my-component>my-component>
div>
<script>
var data = {
counter: 0
};
Vue.component('my-component', {
template: '',
data: function(){
return data;
}
});
var app = new Vue({
el: '#app'
})
script>
组件使用了3次,但是点击任意一个
,3个的数字都会加1,那是因为组件的data引用的是外部的对象,这肯定不是我们期望的效果,所以给组件返回一个新的data对象来独立,示例代码如下:
<div id="app">
<my-component>my-component>
<my-component>my-component>
<my-component>my-component>
div>
<script>
Vue.component('my-component', {
template: '',
data: function(){
return {
couter: 0
};
}
});
var app = new Vue({
el: '#app'
})
script>
这样,点击3个按钮就互不影响了,完全达到复用的目的。
7.2 使用 props 传递数据
在组件中,使用选项 props 来声明需要从父级接收的数据,props 的值可以是两种,一种是字符串数组,一种是对象。
字符串数组写法
基本写法:
<div id="app">
<my-component parent-message="来自父组件的数据">my-component>
div>
<script>
Vue.component('my-component', {
props: ['parentMessage'],
template: '{parentMessage}'
});
var app = new Vue({
el: '#app'
})
script>
如果要传递多个数据,在 props 数组中添加项即可。由于HTML特性不区分大小写,当使用DOM模板时,驼峰命名(camelCase)的props名称要转为短横分隔命名(kebab-case)。
parentMessage
--> parent-message
业务中会经常遇到两种需要改变 prop 的情况,一种是父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改。这种情况可以在组件 data 内再声明一个数据,引用父组件的 prop,示例代码如下:
<div id="app">
<my-component :init-count="1">my-component>
div>
<script>
VUe.component('my-component', {
props: ['initCount'],
template: '{{count}}',
data: function(){
return {
count: this.initCount
}
}
});
var app = new Vue({
el: '#app'
})
script>
组件中声明了数据 count,它在组件初始化时会获取来自父组件的initCount,之后就与之无关了,只用维护count,这样就可以避免直接操作initCount.
另一种情况就是 prop 作为需要被转变的原始值传入。这种情况用计算属性就可以了,示例代码如下:
<div id="app">
<my-component :width="100">my-component>
div>
<script>
Vue.component('my-component', {
props: ['width'],
template: '组件内容',
computed: {
style: function(){
return {
width: this.width + 'px'
}
}
}
});
var app = new Vue({
el: '#app'
})
script>
因为用CSS传递宽度要带单位(px),但是每次都写太麻烦,而且数值计算一般是不带单位的,所以统一在组件内使用计算属性就可以了。
注意,在JavaScript中对象和数组是引用类型,指向同一个内存空间,所以props是对象和数组时,在子组件内改变是会影响父组件的。
对象写法 / Prop 验证
我们上面所介绍的props选项的值都是一个数组,一开始也介绍过,除了数组外,还可以是对象,当 prop 需要验证时,就需要对象写法。
一般当你的组件需要提供给别人使用时,推荐都进行数据验证,比如某个数据必须是数字类型,如果传入字符串,就会在控制台弹出警告。
Vue.component('my-component', {
props: {
propA: Number, //必须是数字类型
propB: [String, Number], //必须是字符串或数字类型
propC: {
type: Boolean,
default: true
}, //布尔值,如果没有定义,默认值就是 true
propD: {
type: Number,
required: true
}, //数字,而已是必传
propE: {
type: Array,
default: function(){
return [];
}
}, //如果是数组或对象,默认值必须从一个工厂函数获取
//自定义一个验证函数
propF: {
validator: function(value){
return value > 10;
}
}
}
});
验证的 type 类型可以是:
- string
- Number
- Boolean
- Object
- Array
- Function
7.3 组件通信
自定义事件
子组件用 $emit()
来触发事件,父组件用 $on()
来监听子组件的事件。
父组件也可以直接在子组件的自定义标签上使用 v-on 来监听子组件触发的自定义事件,示例代码如下:
<div id="app">
<p>总数:{{total}}p>
<my-component @increase="handleGetTotal"
@reduce="handleGetTotal">my-component>
div>
<script>
Vue.component('my-component', {
template: '\
\
\
\
',
data: function(){
return {
counter: 0
}
},
methods: {
handleIncrease: function () {
this.counter++;
this.$emit('increase', this.counter);
},
handleReduce: function () {
this.counter--;
this.$emit('reduce', this.counter);
}
}
});
var app = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
handleGetTotal: function (total) {
this.total = total;
}
}
})
script>
如果你想在某个组件的根元素上监听一个原生事件。可以使用 .native
修饰 v-on 。例如:
<my-component v-on:click.native="doTheThing">my-component>
使用 v-model
组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件。
<input v-model="parentData">
等价于:
<input
:value="parentData"
@input="parentData = $event.target.value"
>
示例:
<div id="app">
<p>总数:{{total}}p>
<my-component v-model="total">my-component>
div>
<script>
Vue.component('my-component', {
template: '',
data: function(){
return {
counter: 0
}
},
methods: {
handleClick: function () {
this.counter++;
this.$emit('input', this.counter);
}
}
});
var app = new Vue({
el: '#app',
data: {
total: 0
}
})
script>
非父子组件通信
使用一个空的 Vue 实例作为中央事件总线(bus),也就是一个中介。
var bus = new Vue();
示例:
<div id="app">
{{ message }}
<component-a>component-a>
div>
<script>
var bus = new Vue();
Vue.component('component-a',{
template: '',
methods: {
handleEvent: function() {
bus.$emit('on-message', '来自组件component-a的内容');
}
}
});
var app = new Vue({
el: '#app',
data: {
message: ''
},
mounted: function() {
var _this = this;
//在实例初始化时,监听来自 bus 实例的事件
bus.$on('on-message', function(msg){
_this.message = msg;
})
}
})
script>
代码解释:首先创建了一个名为 bus 的空 Vue 实例,里面没有任何内容;然后全局定义了组件component-a;最后创建Vue实例app,在app初始化时,也就是在生命周期mounted钩子函数里监听了来自bus的事件on-message,而在组件component-a中,点击按钮会通过bus把事件on-message发出去,此时app就会接收到来自bus的事件,进而在回调里完成自己的业务逻辑。
7.4 使用 slot 分发内容
solt 用法
单个 Solt
在子组件内使用特殊的
元素就可以为这个子组件开启一个 slot(插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的
标签及它的内容。示例代码如下:
<div id="app">
<child-component>
<p>分发的内容p>
<p>更多分发的内容p>
child-component>
div>
<script>
Vue.component('child-component', {
template: '\
\
\
如果父组件没有插入内容,我将作为默认出现
\
\
'
});
var app = new Vue({
el: '#app'
})
script>
具名 Slot
给
元素指定一个 name 后可以分发多个内容,具名 Slot 可以与单个 Slot 共存,例如下面的示例:
<div id="app">
<child-component>
<h2 slot="header">标题h2>
<p>正文内容p>
<p>更多的正文内容p>
<div slot="footer">底部信息div>
child-component>
div>
<script>
Vue.component('child-component', {
template: '\
\
\
\
\
\
\
\