目录
第一章 初识Vue.js
1.1 Vue.js是什么
1.1.1 MVVM模式
Vue.js在设计上使用MVVM模式,MVVM模式是由经典的软件架构MVC衍生而来的,当View(视图层)变化时,会自动更新到ViewModel(视图模型),View和ViewModel之间通过双向绑定建立联系。
1.1.2 Vue.js有什么不同
Vue.js通过MVVM的模式拆分为视图与数据两部分,并将其分离,只需要关心数据,Vue会自动搞定DOM。
1.2 如何使用Vue.js
Vue.js是一个渐进式的JavaScript框架(所谓渐进式,是指可以一步一步、有阶段性的来使用Vue.js,不必一开始就使用所有的东西)。
引入Vue.js框架后,在body底部使用new Vue()的方式创建一个实例。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="book in books">{{book.name}}</li>
</ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
new Vue({
el:'#app',
data:{
books:[
{name:'《vue.js实战》'},
{name:'《JavaScript》权威指南'}
]
}
})
</script>
</body>
</html>
对于一些业务逻辑复杂,对前端工程有要求的项目,可以使用Vue单文件的形式配合webpack使用,必要时还会用到vuex来管理状态,vue-router来管理路由。
第二章 数据绑定和第一个Vue应用
2.1 Vue实例与数据绑定
2.1.1 实例与数据
通过构造函数Vue可以创建一个Vue的根实例,并启动Vue应用:
var app = new Vue({
//选项
})
- el:必不可少,el用于指定一个页面中已存在的DOM元素来挂载Vue实例,它可以是HTMLElement,也可以是CSS选择器。挂载成功后可以通过app.$el来访问该元素,
- data:通过Vue的data选项可以声明应用内需要双向绑定的数据,所有会用到的数据尽量都预先在data内声明,这样不至于将数据散落在业务逻辑中。data还可以指向一个已有的变量,并且它们之间默认建立了双向绑定,当修改其中任意一个时,另一个也会一起变化。
注:v-model指令的值对应于Vue实例的data选项中的name字段。
2.1.2 生命周期
每个Vue实例创建时,都会经历一系列的初始化过程,同时也会调用相应的生命周期的钩子。可以利用这些钩子,在合适的时机执行业务逻辑。
Vue的生命周期钩子:
- created:实例创建完成后调用,此阶段完成了数据的观测等,但尚未挂载,$el还不可以用,可用于初始化处理一些数据。
- mounted:el挂载到实例上后调用,一般第一个业务逻辑会从这里开始。
- beforeDestroy:实例销毁之前调用,主要解绑一些使用addEventListener监听事件等。
这些钩子也可以作为选项写入Vue实例内,并且钩子的this指向的是调用它的Vue实例。
2.1.3 插值与表达式
“{{}}”双大括号是最基本的文本插值方法,它会自动将双向绑定的数据实时显示出来。
如果想输出HTML而不是将数据解释后的纯文本, 可以使用v-html指令。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<span v-html="link"></span>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
//link的内容会被渲染为一个具有点击功能的a标签,而不是纯文本
link:'<a href="#">这是一个链接</a>'
}
})
</script>
</body>
</html>
注:如果将用户产生的内容使用v-html输出后,有可能导致XSS攻击,所以要再服务端对用户提交的内容进行处理,一般可以将尖括号“<>”转义。
如果想显示{{}}标签,而不进行替换,使用v-pre即可跳过这个元素和它的子元素的编译过程。
<span v-pre>{{ 这里的内容是不被编译}}</span>
在{{}}中还可以使用JavaScript表达式进行简单的运算、三元运算等。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
{{ number / 10 }}
{{ isOK ? '确定' : '取消' }}
{{ text.split(',').reverse().join(',') }}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
number:110,
isOK:false,
text:'123,456'
}
})
</script>
</body>
</html>
Vue.js只支持单个表达式,不支持语句和流控制(控制语句的开始、结束,程序的运行),在表达式中,不能使用用户自定义的全局变量,只能使用Vue白名单内的全局变量。
2.1.4 过滤器
Vue.js支持在{{}}插值的尾部添加一个管道符“|”对数据进行过滤,经常用于格式化文本。过滤的规则是自定义的,通过给Vue实例添加选项filters来设置。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
{{ date | formateDate }}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
//在月份、日期、小时等小于10时前补零
var padDate = function (value) {
return value < 10 ? '0' + value : value ;
};
var app = new Vue({
el:'#app',
data:{
date:new Date()
},
filters:{
formateDate:function (value) {//value为需要过滤的数据
var date = new Date(value);
var year = date.getFullYear();
var month = padDate(date.getMonth()+1);
var day = padDate(date.getDay());
var hours = padDate(date.getHours());
var minutes = padDate(date.getMinutes());
var seconds = padDate(date.getSeconds());
//返回整理好的数据
return year + '-' + month + '-' + day + ' ' + hours + ":" + minutes + ":" + seconds;
}
},
mounted:function () {
var _this = this;//声明一个变量指向Vue实例this,保证作用域一致
this.timer = setInterval(function () {
_this.date = new Date();//修改数据date
},1000);
},
beforeDestory:function () {
if (this.timer){
clearInterval(this.timer);//在Vue实例销毁前,清除定时器
}
}
})
</script>
</body>
</html>
过滤器也可以串联,而且可以接收参数。
<!--串联-->
{{ message | filterA | filterB }}
<!--接收参数,字符串arg1和arg2将分别传给过滤器的第二个和第三个参数,因为第一个是数据本身-->
{{ message | filter('arg1', 'arg2')}}
2.2 指令与事件
指令带有前缀v-,指令的主要职责是当其表达式的值改变时,相应的将某些行为应用到DOM上。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<p v-if="show">显示这段文本</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
show:true
},
})
</script>
</body>
</html>
数据驱动DOM是Vue.js的核心理念。
(1)v-bind的基本用途是动态更新HTML元素上的属性,比如id、class等。
链接地址与图片地址都与数据进行了绑定,当通过各种方式改变数据时,链接和图片都会自动更新
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<a v-bind:href="url">链接地址</a>
<img v-bind:src="imgurl">
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
url:'https://www.baidu.com',
imgurl:'http://xxx.xxx.xx/img.jpg'
},
})
</script>
</body>
</html>
(2)v-on:用来绑定事件监听器。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<p v-if="show">显示这段文本</p>
<!-- v-on:click绑定一个点击事件-->
<button v-on:click="handleClose">点击隐藏</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
show:true
},
methods:{
handleClose:function () {
this.show = false;
}
}
})
</script>
</body>
</html>
表达式可以是一个方法名,这些方法否写在Vue实例的methods属性内,并且是函数的形式,函数内的this指向的是当前Vue实例本身,因此可以直接使用this.xxx的形式来访问或修改数据。表达式也可以是一个内联语句,
注:如果要绑定的事件要处理复杂的业务逻辑,建议在methods里声明一个方法,这样可读性更强更好维护。
2.3 语法糖
语法糖是指在不影响功能的情况下,添加某种方法实现同样的效果。
- v-bind可以省略v-bind,直接写一个冒号“:”.
- v-on可以直接用“@”来缩写。
第三章 计算属性
所有的计算属性都以函数的形式写在Vue实例内的computed选项内,最终返回计算的结果。遇到复杂的逻辑时应该使用计算属性。
3.1 计算属性用法
计算属性可以依赖多个Vue实例的数据,只要其中任一数据变化,计算属性就会重新执行,视图也会更新。
当package1或package2中的商品有任何变化,比如购买数量变化或删除商品,计算属性prices就会自动更新,视图中的总价就会自动变化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
总价:{{prices}}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
package1:[
{
name:'iPhone X',
price:6999,
count:2
},
{
name:'xiaomi',
price:2999,
count:1
}
],
package2:[
{
name:'huawei',
price:3999,
count:1
},
{
name:'oppo',
price:4999,
count:2
}
]
},
computed:{
prices:function () {
var prices = 0;
for (var i = 0; i < this.package1.length; i++){
prices += this.package1[i].price * this.package1[i].count;
}
for (var i = 0; i < this.package2.length; i++){
prices += this.package2[i].price * this.package2[i].count;
}
return prices;
}
}
})
</script>
</body>
</html>
计算属性还经常用于动态的设置元素的样式名称class和内联样式style。当使用组件时,计算属性也经常用来动态传递props。
计算属性可以依赖其他计算属性。
计算属性不仅可以依赖当前Vue实例的数据,还可以依赖其他实例的数据。
在app2的计算属性reversedText中,依赖的是app1的数据text,所以当text变化时,实例app2的计算属性也会变化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app1"></div>
<div id="app2">
{{reversedText}}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app1',
data:{
text:'123,456'
}
})
var app2 = new Vue({
el:'#app2',
computed:{
reversedText:function () {
//依赖实例1的数据text
return app1.text.split(',').reverse().join(',');
}
}
})
</script>
</body>
</html>
3.2 计算属性缓存
计算属性是基于它的依赖缓存的。一个计算属性所依赖的数据发生变化时,它才会重新取值。
调用methods里面的方法也可以与计算属性起到同样的作用,methods只要重新渲染,它就会被调用,因此函数也会被执行。
使用计算属性还是methods取决于是否需要缓存,当遍历大数组和做大量计算时,应当使用计算属性。
第四章 b-bind及class与style绑定
4.1 了解v-bind
v-bind主要用法是动态更新HTML元素上的属性。
4.2 绑定class 的几种方式
4.2.1 对象语法
给v-bind:class设置一个对象,可以动态的切换class。
类名active依赖于数据isActive,当其为true时,div会拥有类名active,为false时则没有
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<div :class="{ 'active': isActive }"></div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
isActive:true
}
})
</script>
</body>
</html>
对象中也可以传入多个属性,来动态切换class。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<div class="static" :class="{ 'active': isActive, 'error': isError}"></div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
isActive:false,
isError:true
}
})
</script>
</body>
</html>
当:class的表达式过长或逻辑复杂时,还可以绑定一个计算属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<div :class="classes"></div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
isActive:true,
isError:null
},
computed:{
classes:function () {
return {
active: this.isActive && !this.error,
'text-fail': this.error && this.error.type === 'fail'
}
}
}
})
</script>
</body>
</html>
还可以直接绑定一个Object类型的数据,或者使用类似计算属性的methods。
4.2.2 数组语法
当需要应用多个class时,可以使用数组语法,给:class绑定一个数组,应用一个class列表。
也可以使用三元表达式来根据条件切换class。
样式btn始终会应用,当数据size不为空时,会应用样式前缀btn-,后加size值,当数据为disabled为真时,会应用样式disabled
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<button :class="classes"></button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
size:'large',
disabled:true
},
computed:{
classes:function () {
return [
'btn',{
['btn-' + this.size]: this.size !== '',
['btn-disabled']:this.disabled
}
];
}
}
})
</script>
</body>
</html>
4.2.3 在组件上使用
如果直接在自定义组件上使用class或:class,样式规则会直接应用到这个组件的根元素上。然后在调用这个组件时,应用对象语法或数组语法给组件绑定class。这种用法仅适用于自定义组件的最外层是一个根元素,否则会无效,当不满足这种条件或需要给具体的子元素设置类名时,应当使用组件的props来传递。
4.3 绑定内联样式
使用v-bind:style(即:style)可以给元素绑定内联样式,也有对象语法和数组语法。
使用:style时,Vue.js会自动给特殊的CSS属性名称增加前缀,比如transform。
第五章 内置指令
Vue.js的指令是带有特殊前缀“v-”的HTML特性,它绑定一个表达式,并将一些特性应用到DOM上。
5.1 基本指令
5.1.1 v-cloak
v-cloak不需要表达式,它会在Vue实例结束编译时从绑定的HTML元素上移除,经常和CSS的display:none配合使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" v-cloak>
{{message}}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
message:'这是一段文本'
}
})
</script>
</body>
</html>
在一般情况下,v-cloak是一个解决初始化慢导致页面闪动的最佳实践。
5.1.2 v-once
v-once是一个不需要表达式的指令,作用是定义它的元素或组件只渲染一次,包括元素或组件的所有子节点。首次渲染后,不在随数据的变化重新渲染,将被视为静态内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<span v-once>{{ message }}</span>
<div v-once>{{ message }}</div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
message:'这是一段文本'
}
})
</script>
</body>
</html>
5.2 条件渲染指令
5.2.1 v-if、v-else-if、v-else
Vue.js的条件指令可以根据表达式的值在DOM中渲染或销毁元素/组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<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 src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
status:1
}
})
</script>
</body>
</html>
v-else-if要紧跟v-if,v-else要紧跟v-else-if或v-if,如果一次判断的是多个元素,可以在Vue.js内置的<template>元素上使用条件指令,最终渲染的结果不会包含该元素。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<template v-if="status===1">
<p>这是一段文本</p>
<p>这是一段文本</p>
<p>这是一段文本</p>
</template>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
status:1
}
})
</script>
</body>
</html>
Vue在渲染元素时,出于效率考虑,会尽可能的复用已有的元素而非重新渲染。
键入内容后,点击切换按钮,虽然DOM变了,但是之前在输入框键入的内容并没有改变,只是替换了placeholder的内容,说明<input>被复用了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<template v-if="type==='name'">
<label>用户名:</label>
<input placeholder="输入用户名">
</template>
<template v-else>
<label>邮箱:</label>
<input placeholder="输入邮箱">
</template>
<button @click="handleToggleClick">切换输入类型</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
type:name
},
methods:{
handleToggleClick:function () {
this.type = this.type === 'name' ? 'mail' : 'name';
}
}
})
</script>
</body>
</html>
可以使用Vue.js提供的key属性,它可以决定是否要复用元素,key的值必须是唯一的。
给两个<input>元素都增加key之后,就不会复用了,切换类型时键入的内容也会被删除
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<template v-if="type==='name'">
<label>用户名:</label>
<input placeholder="输入用户名" key="name-input">
</template>
<template v-else>
<label>邮箱:</label>
<input placeholder="输入邮箱" key="mail-input">
</template>
<button @click="handleToggleClick">切换输入类型</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
type:name
},
methods:{
handleToggleClick:function () {
this.type = this.type === 'name' ? 'mail' : 'name';
}
}
})
</script>
</body>
</html>
5.2.2 v-show
v-show是改变元素的CSS属性display。当v-show表达式的值为false时,元素会隐藏,查看DOM结构会看到元素上加载了内联样式display:none。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<p v-show="status===1">当status为1时显示该行</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
status:2
},
})
</script>
</body>
</html>
注:v-show不能在<template>上使用。
5.2.3 v-show和v-if的选择
v-show和v-if具有类似的功能,不过v-if才是真正的条件渲染,它会根据表达式适当的销毁或重建元素及绑定事件或子组件。若表达式初始值为false,则一开始元素/组件并不会渲染,只有当条件第一次变为真时才开始编译。
v-show只是简单的CSS属性切换,无论条件真与否,都会被编译。
v-if更适合条件不经常改变的场景,因为它切换开销相对较大,v-show适用于频繁切换条件。
5.3 列表渲染指令v-for
5.3.1 基本用法
当需要将一个数组遍历或枚举一个对象循环显示时,就会用到列表渲染指令v-for。它的表达式需要结合in使用。
books是数据,book是当前元素的别名,循环出的每个<li>内的元素都可以访问到对应的当前数据book
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<ul>
<li v-for="book in books">{{ book.name }}</li>
</ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
books:[
{name:'《vue.js实战》'},
{name:'《JavaScript》权威指南'}
]
}
})
</script>
</body>
</html>
注:渲染列表也支持用of来代替in作为分隔符。
v-for的表达式支持一个可选参数作为当前项的索引。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<ul>
<!--分隔符前的语句使用括号,第二项就是books,当前项是索引-->
<li v-for="(book, index) in books">{{index}}-{{ book.name }}</li>
</ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app1 = new Vue({
el:'#app',
data:{
books:[
{name:'《vue.js实战》'},
{name:'《JavaScript》权威指南'}
]
}
})
</script>
</body>
</html>
v-for也可以用在内置标签<template>上。
对象的属性也可以遍历,遍历对象的属性时,有两个可选参数,分别是键名和索引。
v-for还可以迭代整数。
5.3.2 数组更新
Vue.js包含了一组观察数组变异的方法,使用它们改变数组也会出发视图更新:push()、pop()、shift()、unshift()、splice()、sort()、reverse()。
不会改变原数组的方法:filter()、concat()、slice()。它们返回的是一个新数组,在使用这些非变异方法时,可以用新数组来替换原数组。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<ul>
<template v-for="book in books">
<li>书名:{{book.name}}</li>
<li>作者:{{book.author}}</li>
</template>
</ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
books:[
{
name:'《vue.js实战》',
author:'梁灏'
},
{
name:'《JavaScript高级程序设计》',
author:'Nicholas C.Zakas'
}
]
}
});
app.books = app.books.filter(function (item) {
return item.name.match(/JavaScript/);
});
</script>
</body>
</html>
Vue在检测到数组变化时,并不是直接重新渲染整个列表,而是最大化的复用DOM元素。替换的数组中,含有相同元素的项不会被重新渲染,因此可以用新数组来替换旧数组。
以下变动的数组中,Vue是不能检测到的,也不会触发视图更新:
- 通过索引直接设置项,比如app.books[3] = {...}。可以使用Vue内置的set方法或者直接用splice来解决这个问题。
- 修改数组长度,比如app.books.length = 1。
5.3.3 过滤与排序
可以使用计算属性来返回过滤或排序后的数组。
计算属性filterBooks依赖books,但是不会修改books
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<ul>
<template v-for="book in filterBooks">
<li>书名:{{book.name}}</li>
<li>作者:{{book.author}}</li>
</template>
</ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
books:[
{
name:'《vue.js实战》',
author:'梁灏'
},
{
name:'《JavaScript高级程序设计》',
author:'Nicholas C.Zakas'
}
]
},
computed:{
filterBooks:function () {
return this.books.filter(function (book) {
return book.name.match(/JavaScript/);
});
}
}
})
</script>
</body>
</html>
按照书名的长度由长到短排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
<ul>
<template v-for="book in sortedBooks">
<li>书名:{{book.name}}</li>
<li>作者:{{book.author}}</li>
</template>
</ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
books:[
{
name:'《vue.js实战》',
author:'梁灏'
},
{
name:'《JavaScript高级程序设计》',
author:'Nicholas C.Zakas'
},
{
name:'《JavaScript语言精粹》',
author:'Douglas Crockford'
}
]
},
computed:{
sortedBooks:function () {
return this.books.sort(function (a, b) {
return a.name.length < b.name.length;
});
}
}
})
</script>
</body>
</html>
5.4 方法与事件
5.4.1 基本用法
监听一个按钮的点击事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
点击次数:{{ counter }}
<button @click="counter++">+1</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
counter:0
}
})
</script>
</body>
</html>
@click的表达式可以直接使用JavaScript语句,也可以是一个Vue实例中methods选项内的函数名。
上例中增加一个按钮
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" >
点击次数:{{ counter }}
<button @click="handleAdd">+1</button>
<button @click="handleAdd(10)">+10</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
counter:0
},
methods:{
handleAdd:function (count) {
count = count || 1;
this.counter += count;
}
}
})
</script>
</body>
</html>
@click调用的方法名后可以不跟括号,此时如果该方法有参数,默认会将原生事件对象event传入。
Vue提供了一个特殊变量$event,用于访问原生DOM事件。
5.4.2 修饰符
Vue支持一下修饰符:.stop、.prevent、.capture、.self、.once。
<!--阻止单击事件冒泡-->
<a @click.stop="handle"></a>
<!--提交事件不再重载页面-->
<form @submit.prevent="handle"></form>
<!-- 修饰符可以串联-->
<a @click.stop.prevent="handle"></a>
<!--只有修饰符-->
<form @submit.prevent></form>
<!--添加事件侦听器时使用事件捕获模式-->
<div @click.capture="handle">...</div>
<!--只当事件在该元素本身(而不是子元素)触发时触发回调-->
<div @click.self="handle"></div>
<!--只触发一次,组件同样适用-->
<div @click.once="handle"></div>
在表单元素上监听事件时,还可以使用按键修饰:
<!--只有在keyCode为13时调用vm.submit()-->
<input @keyup.13="submit">
也可以配置具体按键:
<!--全局定义后,就可以使用@keyup.f1-->
Vue.config.keyCodes.f1 = 112;
Vue提供的快捷名称:.enter、.tab、.delete(捕获删除和退格键)、.esc、.space、.up、.down、.left、.right。这些按键修饰符也可以组合使用,或和鼠标一起配合使用:.ctrl、.alt、.shift、.meta。
<!--Shift + S-->
<input @keyup.shift.83="handleSave">
<!--Ctrl + Click-->
<div @click.ctrl="doSomething">Do Something</div>
5.5 利用计算属性、指令等开发购物车
5.5.1 需求
购物车需要展示一个已加入购物车的商品列表,包含商品名称、商品单价、购买数量和操作等信息,实时显示购买的总价。其中购买数量可以增加或减少,每类商品可以从购物车中移除。
5.5.2 实现代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用vue.js实现购物车功能</title>
<style>
[v-cloak]{
display: none;
}
table{
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
}
th,td{
padding: 8px 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
th{
background: #f7f7f7;
color:#5c6b77;
font-weight: 600;
white-space: nowrap;
}
</style>
</head>
<body>
<div id="app" v-cloak>
<template v-if="list.length">
<table>
<thead>
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>商品数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in list">
<td>{{ index +1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.price }}</td>
<!--增减数量-->
<td>
<button @click="handleReduce(index)" :disable="item.count===1">-</button>
{{ item.count }}
<button @click="handleAdd(index)">+</button>
</td>
<td>
<button @click="handleRemove(index)">移除</button>
</td>
</tr>
</tbody>
</table>
<div>总价:¥ {{ totalPrice }}</div>
</template>
<!--通过判断数组list的长度来实现当列表为空时,在页面显示“购物车为空”的提示-->
<div v-else>购物车为空</div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
list:[
{
id:1,
name:'apple x',
price:6999,
count:1
},
{
id:2,
name:'oppo',
price:4999,
count:1
},
{
id:3,
name:'xiaomi',
price:1999,
count:1
}
]
},
computed:{
totalPrice:function () {
var total = 0;
for (var i = 0; i < this.list.length; i++){
var item = this.list[i];
total += item.price * item.count;
}
//使用正则表达式转换千位分隔符
return total.toString().replace(/\B(?=(\d{3})+$)/g,',');
}
},
methods:{
handleReduce:function (index) {
if(this.list[index].count===1) return;
this.list[index].count--;
},
handleAdd:function (index) {
this.list[index].count++;
},
handleRemove:function (index) {
this.list.splice(index,1);
}
}
})
</script>
</body>
</html>
第六章 表单与v-model
6.1 基本用法
Vue.js提供了v-model指令,用于在表单类元素上双向绑定数据。
在输入框输入的同时,{{ message }}也会实时将内容渲染在视图中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="text" v-model="message" placeholder="请输入">
<p>输入的内容为:{{ message }}</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
message:''
}
})
</script>
</body>
</html>
使用v-model后,表单控件显示的值只依赖所绑定的数据,不再关心初始化时value属性。使用v-model时,在拼音阶段,Vue是不会更新数据的,当敲下汉字才会触发更新。如果要总是实时更新,可以用@input来替代v-model。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="text" @input="handleInput" placeholder="请输入">
<p>输入的内容为:{{ message }}</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
message:''
},
methods:{
handleInput:function (e) {
this.message = e.target.value;
}
}
})
</script>
</body>
</html>
6.1.1 单选按钮
单选按钮在单独使用时,不需要v-model,直接使用v-bind绑定一个布尔类型的值,为真时选中,为否时不选。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="radio" :check="picked">
<label>单选按钮</label>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
picked:true
},
})
</script>
</body>
</html>
如果是组合使用来实现互斥选择的效果,需要v-modle配合value使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="radio" v-model="picked" value="html" id="html">
<label for="html">HTML</label>
<br>
<input type="radio" v-model="picked" value="js" id="js">
<label for="js">JavaScript</label>
<br>
<input type="radio" v-model="picked" value="css" id="css">
<label for="css">CSS</label>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
picked:'js'
},
})
</script>
</body>
</html>
6.1.2 复选框
复选框单独使用时,用v-model来绑定一个布尔值。
在勾选时,数据checked的值变为了true,label中渲染的内容也会更新。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="checkbox" v-model="checked" id="checked">
<label for="checked">选择状态:{{ checked }}</label>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
checked:false
}
})
</script>
</body>
</html>
组合使用时,使用v-model和value一起,多个勾选框都绑定到同一个数组类型的数据,value的值在数组当中,就会选中这一项,这一过程是双向的,在勾选时,value的值也会自动push到这个数组中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="checkbox" v-model="checked" value="html" id="html">
<label for="html">HTML</label>
<br>
<input type="checkbox" v-model="checked" value="js" id="js">
<label for="js">JavaScript</label>
<br>
<input type="checkbox" v-model="checked" value="css" id="css">
<label for="css">CSS</label>
<p>选择的项是:{{ checked }}</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
checked:['html']
},
})
</script>
</body>
</html>
6.1.3 选择列表
单选:
<option>是备选项,如果含有value属性,v-model就会优先匹配value的值,如果没有,就会直接匹配<option>的text。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<select v-model="selected">
<option>html</option>
<option>js</option>
<option>css</option>
</select>
<p>选择的项是:{{ selected }}</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
selected:''
}
})
</script>
</body>
</html>
给<select>添加multiple属性可以实现多选:
<select v-model="selected" multiple>
使用v-for动态输出<option>:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<select v-model="selected">
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>
<p>选择的项是:{{ selected }}</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
selected:'html',
options:[
{
text:'HTML',
value:'html'
},
{
text:'JavaScript',
value:'js'
},
{
text:'CSS',
value:'css'
}
]
}
})
</script>
</body>
</html>
可以使用v-bind实现绑定一个动态的数据。
6.2 修饰符
v-model的修饰符用于控制数据同步的时机。
6.2.1 .lazy
在输入框中,v-model默认是在input事件中同步输入框的数据,使用修饰符.lazy会转变为在change时间中同步。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="text" v-model.lazy="message">
<p>{{ message }}</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
message:''
}
})
</script>
</body>
</html>
6.2.2 .number
使用修饰符.number可以将输入转换为Number类型,否则输入的是数字,但它的类型其实是String。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="number" v-model.number="message">
<p>{{ typeof message }}</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
message:123
}
})
</script>
</body>
</html>
6.2.3 .trim
修饰符.trim可以自动过滤输入的首尾空格
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="text" v-model.trim="message">
<p>{{ message }}</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
message:''
}
})
</script>
</body>
</html>
第七章 组件详解
7.1 组件与复用
7.1.1 组件的用法
组件需要注册后才可以使用。注册有全局注册和局部注册两种方式。全局注册后,任何Vue实例都可以使用。全局注册示例:
//my-component为注册的组件自定义标签名称
Vue.component('my-component',{
//选项
})
要在父实例中使用这个组件,必须要在实例创建前注册,之后就可以用<my-component></my-component>的形式来使用组件。
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
//选项
});
var app = new Vue({
el:'#app'
});
</script>
在组件选项中添加template可以显示组件内容。template的DOM结构必须被一个元素包含。
Vue.component('my-component',{
template:'<div>这里是组件内容</div>'
});
在Vue实例中,使用components选项可以在局部注册组件,注册后的组件只有在该实例作用域下有效。组件中也可以使用components选项来注册组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<my-component></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var Child = {
template:'<div>局部注册组件的内容</div>'
};
var app = new Vue({
el:'#app',
components:{
'my-component':Child
}
})
</script>
</body>
</html>
注:Vue组件的模板在某些情况下会受到HTML的限制,这种情况下,可以使用特殊的is属性来挂载组件。
data选项必须是函数,然后将数据return出去。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<my-component></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
template:'<div>{{ message }}</div>',
data:function () {
return{
message:'组件内容'
}
}
});
var app = new Vue({
el:'#app'
})
</script>
</body>
</html>
注:JavaScript对象是引用关系,所以如果return出的对象引用了外部的一个对象,那这个对象就是共享的,任何一方修改都会同步。
7.2 使用props传递数据
7.2.1 基本用法
组件不仅仅是要把模板的内容进行复用,更重要的是组件之间要进行通信。通常父组件的模板中包含子组件,父组件要正向的向子组件传递数据或参数,子组件接收到后根据参数的不同来渲染不同的内容或执行操作。这个正向传递数据的过程是通过props来实现的。
在组件中,使用选项props来声明需要从父级接收的数据,props的值可以是两种,一种是字符串数组,一种是对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<my-component message="来自父组件的数据"></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
props:['message'],
template:'<div>{{ message }}</div>',
});
var app = new Vue({
el:'#app'
})
</script>
</body>
</html>
props中声明的数据与组件data函数return的数据组要的区别是props的来自父级,而data中的是组件自己的数据,作用域是组件本身,这两种数据都可以在模板template及计算属性computed和方法methods中使用。在组件的自定义标签上直接写该props的名称,如果要传递多个数据,在props数组中添加即可。
可以使用指令v-model来动态绑定props的值,当父组件的数据变化时,也会传递给子组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="text" v-model="parentMessage">
<my-component :message="parentMessage"></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
props:['message'],
template:'<div>{{ message }}</div>',
});
var app = new Vue({
el:'#app',
data:{
parentMessage:''
}
})
</script>
</body>
</html>
7.2.2 单向数据流
Vue 2.x通过props传递数据是单向的,也就是父组件数据变化时会传递给子组件,但是反过来不行。
7.2.3 数据验证
当props需要验证时,需要对象写法。验证的type类型可以是:String、Number、Boolean、Object、Array、Function。type也可以是一个自定义构造器,使用instanceof检测。
7.3 组件通信
组件关系可以分为父子组件通信、兄弟组件通信、跨级组件通信。
7.3.1 自定义事件
当组件需要向父组件传递数据时,需要用到自定义事件。v-on可以用于组件之间的自定义事件。子组件用$emit()来触发事件,父组件用$on()监听子组件事件。父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<p>总数:{{ total }}</p>
<my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
template:'\
<div>\
<button @click="handleIncrease">+1</button>\
<button @click="handleReduce">-1</button>\
</div>',
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>
</body>
</html>
v-on也可以监听DOM事件,用.native修饰符表示监听的是一个原生事件,监听的是该组件的根元素。
7.3.2 使用v-model
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<p>总数:{{ total }}</p>
<my-component v-model="total"></my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
template:'<button @click="handleClick">+1</button>',
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>
</body>
</html>
v-model可以用来创建自定义的表单输入组件,进行数据双向绑定。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<p>总数:{{ total }}</p>
<my-component v-model="total"></my-component>
<button @click="handleReduce">-1</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
props:['value'],
template:'<input :value="value" @input="updateValue">',
methods:{
updateValue:function (event) {
this.$emit('input',event.target.value);
}
}
});
var app = new Vue({
el:'#app',
data:{
total:0
},
methods:{
handleReduce:function () {
this.total--;
}
}
})
</script>
</body>
</html>
实现一个具有双向绑定的v-model组件要满足以下两个要求:
- 接收一个value属性。
- 在有新的value时触发input时间。
7.3.3 非父子组件通信
非父子组件一般有两种:兄弟组件和跨级组件。
(1)中央时间总线bus
首先创建一个名为bus的空Vue实例,然后全局定义组件component-a,最后创建Vue实例app,在app初始化时,也就是生命周期mounted钩子函数里监听了来自bus的时间on-message,而在组件component-a中,点击按钮会通过bus把时间on-message发出去,此时app就会收到来自bus的时间,进而在回调里完成自己的业务逻辑。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<p>{{ message }}</p>
<component-a></component-a>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var bus = new Vue();
Vue.component('component-a',{
template:'<button @click="handleEvent">传递事件</button>',
methods:{
handleEvent:function () {
bus.$emit('on-message','来自组件component-a的内容');
}
}
});
var app = new Vue({
el:'#app',
data:{
message:''
},
mounted:function () {
var _this = this;
bus.$on('on-message',function (msg) {
_this.message = msg;
});
}
})
</script>
</body>
</html>
(2)父链
在子组件中,使用this.$parent可以直接访问该组件的父实例或组件,父组件也可以通过this.$children访问它所有的子组件,而且可以递归向上或 向下无限访问,直到根实例或最内层的组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<p>{{ message }}</p>
<component-a></component-a>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('component-a',{
template:'<button @click="handleEvent">通过父链直接修改数据</button>',
methods:{
handleEvent:function () {
this.$parent.message = '来自组件component-a的内容';
}
}
});
var app = new Vue({
el:'#app',
data:{
message:''
}
})
</script>
</body>
</html>
注:在业务中,子组件应该尽量避免依赖父组件的数据,更不应该去主动修改它的数据,因为这样使得父子组件紧耦合。父子组件最好还是通过props和$emit来通信。
(3)子组件索引
可以用特殊属性ref来为子组件指定一个索引名称。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<button @click="handleRef">通过ref获取子组件实例</button>
<component-a ref="comA"></component-a>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('component-a',{
template:'<div>子组件</div>',
data:function () {
return{
message:'子组件内容'
}
}
});
var app = new Vue({
el:'#app',
methods:{
handleRef:function () {
var msg = this.$refs.comA.message;
console.log(msg);
}
}
})
</script>
</body>
</html>
在父组件模板中,子组件标签上使用ref指定一个名称,并在父组件内通过this.$refs来访问指定名称的组件。
7.4 使用slot分发内容
7.4.1 什么是slot
当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot,这个过程叫做内容分发。
props传递数据、events触发时间和slot分发内容构成了Vue组件的3个API来源。
7.4.2 作用域
父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。
slot分发的内容,作用域是在父组件上。
7.4.3 slot用法
(1)单个slot
在子组件内使用特殊的<slot>元素就可以为这个子组件开启一个slot,在父组件模板里,插入在子组件标签内的所有内容将替代子组件的<slot>标签及它的内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<child-component>
<p>分发的内容</p>
<p>更多分发的内容</p>
</child-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('child-component',{
template:'\
<div>\
<slot>\
<p>如果父组件没有插入内容,我将作为默认出现</p>\
</slot>\
</div>\
',
});
var app = new Vue({
el:'#app',
})
</script>
</body>
</html>
注:子组件<slot>内的备用内容,它的作用域是子组件本身。
(2)具名slot
给<slot>元素指定一个name后可以分发多个内容,具名slot可以与单个slot共存。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<child-component>
<h1 slot="header">标题</h1>
<p>正文内容</p>
<p>更多的正文内容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('child-component',{
template:'\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot></slot>\
</div> \
<div class="footer">\
<slot name="footer"></slot>\
</div>\
</div>',
});
var app = new Vue({
el:'#app',
})
</script>
</body>
</html>
没有name属性的<slot>将作为默认slot出现,父组件没有使用slot特性的元素与内容都将在这里出现。如果没有指定默认的匿名slot,父组件内做鱼的内容片断都将被抛弃。
7.4.4 作用域插槽
作用域插槽是一种特殊的slot,使用一个可以复用的模板替换已渲染的元素。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<child-component>
<!--props只是一个临时变量,template内可以通过临时变量props访问来自子组件插槽的数据-->
<template scope="props">
<p>来自父组件的内容</p>
<p>{{ props.msg }}</p>
</template>
</child-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('child-component',{
template:'\
<div class="container">\
<slot msg="来自子组件的内容"></slot>\
</div>',
});
var app = new Vue({
el:'#app',
})
</script>
</body>
</html>
使用作用域插槽实现列表组件,允许组件自定义应该如何渲染列表每一项。
子组件my-list接收一个来自父级prop数组books,并且将它在name为book的slot上使用v-for指令循环,同时暴露一个变量bookName。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<my-list :books="books">
<template slot="book" scope="props">
<li>{{ props.bookName }}</li>
</template>
</my-list>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-list',{
props:{
books:{
type:Array,
default:function () {
return [];
}
}
},
template:'\
<ul>\
<slot name="book"\
v-for="book in books"\
:book-name="book.name">\
</slot>\
</ul>',
});
var app = new Vue({
el:'#app',
data:{
books:[
{name:'《Vue.js实战》'},
{name:'《JavaScript语言精粹》'}
]
}
})
</script>
</body>
</html>
作用域插槽的使用场景是既可以复用子组件的slot,又可以使slot内容不一致。
7.4.5 访问slot
Vue.js 2.x提供了用来访问被slot分发的内容的方法$slots,通过$slots可以访问某个具名slot。slot主要用于独立组件开发中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<child-component>
<h1 slot="header">标题</h1>
<p>正文内容</p>
<p>更多的正文内容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('child-component',{
template:'\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot></slot>\
</div>\
<div class="footer">\
<slot name="footer"></slot>\
</div>\
</div>',
mounted:function () {
var heander = this.$slots.header;
var main = this.$slots.main;
var footer = this.$slots.footer;
console.log(footer);
console.log(footer[0].elm.innerHTML);
}
});
var app = new Vue({
el:'#app',
data:{
books:[
{name:'《Vue.js实战》'},
{name:'《JavaScript语言精粹》'}
]
}
})
</script>
</body>
</html>
7.5 组件高级用法
7.5.1 递归组件
组件在它的模板内可以递归的调用自己,只要给组件设置name的选项就可以了。设置name后,在组件模板内就可以递归使用了,不过必须要给一个条件来限制递归的数量,否则会抛出错误。组件递归使用可以用来开发一些具有未知层级关系的独立组件,比如级联选择器和树形控件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<child-component :count="1">
</child-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('child-component',{
name:'child-component',
props:{
count:{
type:Number,
default:1
}
},
template:'\
<div class="child">\
<child-component\
:count="count+1"\
v-if="count<3"></child-component>\
</div>',
});
var app = new Vue({
el:'#app',
})
</script>
</body>
</html>
7.5.2 内联模板
组件的模板一般都是在template选项内定义的,Vue提供了一个内联模板的功能,在使用组件时,给组件标签使用inline-template特性,组件就会把它的内容当做模板,而不是把它的内容分发。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<child-component inline-template>
<div>
<h2>在父组件中定义子组件的模板</h2>
<p>{{ message }}</p>
<p>{{ msg }}</p>
</div>
</child-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('child-component',{
data:function () {
return {
msg:'在子组件声明的数据'
}
}
});
var app = new Vue({
el:'#app',
data:{
message:'在父组件声明的数据'
}
})
</script>
</body>
</html>
注:在子组件和父组件中声明的数据如果同名,优先使用子组件的数据。
7.5.3 动态组件
Vue.js提供了<component>用来动态的挂载不同的组件,使用is特性来选择要挂载的组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<component :is="currentView">
<button @click="handleChangeView('A')">切换到A</button>
<button @click="handleChangeView('B')">切换到A</button>
<button @click="handleChangeView('C')">切换到A</button>
</component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
components:{
comA:{
template:'<div>组件A</div>'
},
comB:{
template:'<div>组件B</div>'
},
comC:{
template:'<div>组件C</div>'
}
},
data:{
currentView:'comA'
},
methods:{
handleChangeView:function (component) {
this.currentView = 'com' + component;
}
}
})
</script>
</body>
</html>
7.5.4 异步组件
Vue.js允许将组件定义为一个工厂函数,动态的解析组件。Vue.js只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<child-component >
</child-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue .component('child-component',function (resolve, reject) {
window.setTimeout(function () {
resolve({
template:'<div>我是异步渲染</div>'
});
},2000);
});
var app = new Vue({
el:'#app',
})
</script>
</body>
</html>
7.6 其他
7.6.1 $nextTick
Vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。然后在下一个事件循环tick中,Vue刷新队列并执行十几(已去重的)工作。所以如果用一个for循环来动态改变数据100遍,其实它只会应用最后一次改变。
$nextTick是用来知道什么时候DOM更新完成的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<div id="div" v-id="showDiv">这是一段文本</div>
<button @click="getText">获取div内容</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
showDiv:false
},
methods:{
getText:function () {
this.showDiv = true;
this.$nextTick(function () {
var text = document.getElementById('div').innerHTML;
console.log(text);
})
}
}
})
</script>
</body>
</html>
7.6.2 X-Templates
Vue另一种定义模板的方式:在<script>标签里使用text/template类型,并且指定一个id,将这个id赋给template。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<my-component>
<script type="text/x-template" id="my-component">
<div>这是组件内容</div>
</script>
</my-component>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
template:'#my-component'
})
var app = new Vue({
el:'#app',
})
</script>
</body>
</html>
7.6.3 手动挂载实例
Vue提供了Vue.extend和$mount两个方法来手动挂载一个实例。
Vue.extend是基础Vue构造器,创建一个“子类”,参数是一个包含组件选项的对象。
如果Vue实例在实例化时没有收到el选项,它就出于“未挂载”状态,没有关联的DOM元素。可以使用$mount()手动的挂载一个未挂载的实例。这个方法返回实例自身, 因而可以链式调用其他实例方法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var MyComponent = Vue.extend({
template:'<div>Hello:{{ name }}</div>',
data:function () {
return {
name:'xu'
}
}
});
new MyComponent().$mount('#app');
</script>
</body>
</html>
其他两种写法:
new MyComponent({
el:'#app'
});
//在文档之外渲染并且随后挂载
var component = new MyComponent().$mount();
document.getElementById('app').appendChild(component.$el);
7.7 开发一个数字输入框组件
7.7.1 需求
数字输入框只能输入数字,而且有两个快捷按钮,可以直接加1或减1,还可以设置初始值、最大值、最小值,在数值改变时,触发一个自定义事件来通知父组件。
7.7.2 实现代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数字输入框组件</title>
</head>
<body>
<div id="app">
<!--默认值5,最大值10,最小值0-->
<input-number v-model="value" :max="10" :min="0"></input-number>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
function isValueNumber(value){
return (/(^-?[0-9]+\.{1}\d+$)|(^-?[1-9][0-9]*$)|(^-?0{1}$)/).test(value + '');
}
Vue.component('input-number',{
template:'\
<div class="input-number">\
<input \
type="text"\
:value="currentValue" \
@change="handleChange">\
<button \
@click="handleDown" \
:disabled="currentValue <= min">-</button>\
<button \
@click="handleUp" \
:disabled="currentValue >= max">+</button>\
</div>',
props:{
max:{
//默认最大值:正无穷大
type:Number,
default:Infinity
},
min:{
//默认最小值:负无穷大
type:Number,
default:Infinity
},
value:{
//默认值为0
type:Number,
default:0
}
},
data:function () {
return {
currentValue:this.value
}
},
//监听,当prop或data发生改变时,就会触发watch配置的函数
watch:{
//监听currentValue是为了当currentValue改变时,更新value
currentValue:function (val) {
//在使用v-model时改变value
this.$emit('input',val);
//触发自定义事件on-change,用于告知父组件数字输入框的值改变了
this.$emit('on-change',val);
},
//监听Value是为了知晓从父组件修改了Value
value:function (val) {
this.updateValue(val);
}
},
methods:{
handleDown:function () {
if(this.currentValue <= this.min) return;
this.currentValue -= 1;
},
handleUp:function () {
if(this.currentValue >= this.max) return;
this.currentValue += 1;
},
//用来过滤出一个正确的Value
updateValue:function (val) {
if (val > this.max) val = this.max;
if (val < this.min ) val = this.min;
this.currentValue = val;
},
handleChange:function (event) {
var val = event.target.value.trim();
var max = this.max;
var min = this.min;
if (isValueNnumber(val)) {
val = Number(val);
this.currentValue = val;
if (val > max) {
this.currentValue = max;
} else if (val < min) {
this.currentValue = min;
}
} else {
event.target.value = this.currentValue;
}
}
},
//初始化时对value进行过滤
mounted:function () {
this.updateValue(this.value);
}
});
var app = new Vue({
el:'#app',
data:{
//给定默认值
value:5
}
});
</script>
</body>
</html>
第八章 自定义指令
8.1 基本用法
自定义指令分全局注册和局部注册。
//全局注册
Vue.directive('focus',{
//指令选项
});
//局部注册
var app = new Vue({
el:'#app',
directives:{
focus:{
//指令选项
}
}
})
自定义指令的选项由几个钩子函数组成的,每个都是可选的:
- bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
- inserted:被绑定元素插入父节点时调用。
- update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
- componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
- unbind:只调用一次,指令与元素解绑时调用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<input type="text" v-focus>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.directive('focus',{
inserted:function () {
//聚焦元素
el.focus();
}
});
var app = new Vue({
el:'#app',
})
</script>
</body>
</html>
每个钩子可用的参数:
(1)el:指令所绑定的元素,可以直接用来操作DOM。
(2)binding:一个对象,包含以下属性:
①name:指令名,不包含 v-前缀。
②value:指令的绑定值。
③oldValue:指令绑定的前一个值,仅在update和componentUpdated中可用。
④expression:绑定值的字符串形式。
⑤arg:传给指令的参数。
⑥modifiers:一个包含修饰符的对象。
(3)vnode:Vue编译生成的虚拟节点。
(4)oldVnode:上一个虚拟节点积攒update和componentUpdated钩子中可用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app">
<div v-test:msg.a.b="message"></div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.directive('test',{
bind:function (el, binding, vnode) {
var keys = [];
for (var i in vnode){
keys.push(i);
}
el.innerHTML =
'name:' + binding.name + '<br>' +
'value:' + binding.value + '<br>' +
'expression:' + binding.expression + '<br>' +
'argument:' + binding.argument + '<br>' +
'modifiers:' + JSON.stringify(binding.modifiers) + '<br>' +
'vnode keys:' + keys.join(', ') + '<br>'
}
});
var app = new Vue({
el:'#app',
data:{
message:'some text'
}
})
</script>
</body>
</html>
8.2 开发一个可从外部关闭的下拉菜单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuetest</title>
</head>
<body>
<div id="app" v-cloak>
<div class="main" v-clickoutside="handleClose">
<button @click="show =! show">点击显示下拉菜单</button>
<div class="dropdown" v-show="show">
<p>下拉框的内容,点击外面区域可以关闭</p>
</div>
</div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.directive('clickoutside',{
bind:function (el, binding, vnode) {
function documentHandler(e) {
if(el.contains(e.target)){
return false;
}
if(binding.expression){
binding.value(e);
}
}
el._vueClickOutSide_ = documentHandler;
document.addEventListener('click',documentHandler);
},
unbind:function (el, binding) {
document.removeEventListener('click',el._vueClickOutSide_);
delete el._vueClickOutSide_;
}
});
var app = new Vue({
el:'#app',
data:{
show:false
},
methods:{
handleClose:function () {
this.show = false;
}
}
});
</script>
</body>
</html>