模板语法
插值:mustache语法
数据绑定最常见的形式就是使用Mustache
语法 (双大括号) 的文本插值
<span>Message: {{ msg }}</span>
<span>Message: {{ Firstname+lastName }}</span>
插值表达式,可以是一个变量,也可以是一个表达式,可以进行简单的计算
但一般不推荐这么干,Vue中有更优雅的实现方式
v-once
<div id=app>
<h1 v-once>{{message}}</h1>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue'
}
})
</script>
被v-once
修饰后,{{message}}
只会在初始化获取一次值,后续message
的变化,不会影响dom
中的数据展示
v-bind
有了插值表达式,可以让dom标签的内容实现动态化,但是我们想让dom标签的属性动态化该怎么办?
我们使用v-bind
,将dom标签的属性进行绑定,实现动态
<div id=app>
<img src="imgUrl" alt="测试">
<img v-bind:src="imgUrl" alt="测试">
</div>
<script>
let app = new Vue({
el: '#app',
data: {
imgUrl: 'http://tuchuang.zhangln.com/xbVhJM.png'
}
})
</script>
实际被浏览器解析的html如下
因为使用了v-bind
指令,src的值可以动data中获取了
- 缩写
<img v-bind:src="imgUrl" alt="测试">
可以缩写成<img :src="imgUrl" alt="测试">
即v-bind
是可以省略的,保留:
即可
案例:v-bind绑定class
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Document</title>
<style>
.active1 {
color: red;
}
.active2 {
color: gold;
}
</style>
</head>
<body>
<div id=app>
<h2 :class="active">{{message}}</h2>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue',
active: 'active1'
}
})
</script>
</body>
</html>
通过修改active的值,动态的将不同的样式绑定到了h2标签
- 对象语法
<div id=app>
<h2 :class="{active1:isActive1,active2:isActive2}">{{message}}</h2>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue',
isActive1: true,
isActive2: false
}
})
</script>
active1
和active2
是两个样式,通过isActive1和isActive2的布尔值,确定是否需要将对应的样式添加上来。
如果一次性将一个json对象添加在class上,代码有点复杂,我们可以使用计算属性(computed,后续讲到)或methods中
<div id=app>
<h2 :class="getClass()">{{message}}</h2>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue',
isActive1: true,
isActive2: false
},
methods: {
getClass: function () {
return {active1: this.isActive1, active2: this.isActive2};
}
}
})
</script>
当我们的需求中,需要对一个元素不停的切换样式的时候,就可以使用这种办法。
- 数组语法
作用和对象语法一样,用的比较少,我们来简单做个演示
<div id=app>
<h2 :class="classes">{{message}}</h2>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue',
classes:['active1','active2']
}
})
</script>
相当于把两个样式都绑定给了标签
与对象语法一样,数组语法也可以使用计算属性或methods获取
案例:v-bind结合v-for实现一个小需求
需求
:在用v-for
生成的列表中,点击哪个元素,哪个元素就变色
<body>
<div id=app>
<ul>
<li v-for="(item,index) in movies" @click="changeClass(index)" :class="getClass(index)">{{index}} - {{item}}</li>
</ul>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
movies: ['海王', '海尔兄弟', '火影忍者', '进击的巨人'],
isActive: [false, false, false, false]
},
methods: {
getClass: function (index) {
console.log('计算' + index + '的class');
return {active1: this.isActive[index]};
},
changeClass: function (index) {
console.log('点击了' + index);
console.log('当前配置:' + this.isActive);
//数组的深拷贝
let newIsActive = JSON.parse(JSON.stringify(this.isActive));
for (let i = 0; i < newIsActive.length; i++) {
newIsActive[i] = i == index ? true : false;
}
console.log('新的配置:' + newIsActive);
this.isActive = newIsActive;
}
}
})
</script>
</body>
- 更简洁的写法
<div id=app>
<ul>
<li v-for="(item,index) in movies" @click="changeClass(index)" :class="getClass(index)">{{index}} - {{item}}</li>
</ul>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
movies: ['海王', '海尔兄弟', '火影忍者', '进击的巨人'],
currentIndex: -1
},
methods: {
getClass: function (index) {
return index == this.currentIndex ? {active1: true} : {active1: false};
},
changeClass: function (index) {
this.currentIndex = index;
}
}
})
</script>
案例:v-bind绑定style
- 对象语法
<h2 :style="{fontSize:'50px'}">{{message}}</h2>
<div id=app>
<h2 :style="{fontSize:finalSize+'px'}">{{message}}</h2>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue',
finalSize: 50
}
})
</script>
觉得style属性太长了的话,可以抽取成方法
<div id=app>
<h2 :style="getStyles()">{{message}}</h2>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue',
finalSize: 50
},
methods: {
getStyles: function () {
return {fontSize: this.finalSize + 'px'};
}
}
})
</script>
- 数组语法
<div id=app>
<h2 :style="[style1,style2]">{{message}}</h2>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue',
style1: {fontSize: this.finalSize + 'px'},
style2: {backgroundColor: 'red'},
}
})
</script>
这个数组语法,就和绑定class的数组语法非常像了,只不过class的时候,每个数组元素是一个class样式,这里的每个数组元素是一个对象类型的内联样式
计算属性与侦听器
计算属性:computed
<body>
<div id=app>{{fullName}}</div>
<script>
let app = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName: function () {
return this.firstName + this.lastName;
}
}
})
</script>
当插值表达式中的结果计算逻辑比较复杂的时候,就可以使用计算属性
计算属性的setter和getter方法
每个计算属性其实都包含了一个setter和getter方法
上例中,我们就使用了getter方法来进行读取,有时候也可以提供setter方法进行设置
<div id=app>{{fullName}}</div>
<script>
let app = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName: {
get() {
console.log("调用了get");
return this.firstName + " " + this.lastName;
},
set(newValue) {
console.log("调用了set");
const names = newValue.split(" ");
this.firstName = names[0];
this.lastName = names[1];
}
}
}
})
</script>
setter方法会在设置fullName值的时候被调用
计算属性和methods对比
- 计算属性会监控所依赖的数据的值是否发生了变化,只有变化了,才会执行计算
- methods每次模板编译都会执行。只要有响应式属性改变,视图刷新,函数就执行
侦听器:watch
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
<div id=app>{{fullName}}</div>
<script>
let app = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三',
fullName: ''
},
watch: {
//如果firstName发生变化,则函数执行
firstName() {
this.fullName = this.firstName + this.lastName;
}
}
})
</script>
Class和Style绑定
条件渲染
v-if
、v-else-if
、v-else
<div id=app>
<div v-if="isA">A</div>
<div v-else-if="isB">B</div>
<div v-else>C</div>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
isA: true,
isB: false
}
})
</script>
案例:切换登陆方式
<div id=app>
<div v-if="showUserName">
<input type="text" placeholder="用户名..." key="u1">
<button>登陆</button>
</div>
<div v-else-if="showEmail">
<input type="text" placeholder="邮箱..." key="u2">
<button>登陆</button>
</div>
<button @click="changeLoginWay">切换</button>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
showUserName: true,
showEmail: false
},
methods: {
changeLoginWay() {
this.showUserName = !this.showUserName;
this.showEmail = !this.showEmail;
}
}
})
</script>
这里为两个input设置了key,并且设置key的值不同。
是为了不想让Vue复用input。
这里的原理性解释涉及到Vue的虚拟DOM
,后续再说
v-show
<div id=app>
<h2 v-show="isShow">{{message}}</h2>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue',
isShow: false
}
})
</script>
- v-show与v-if比较
与v-if类似,v-show也可以用于决定元素的显示与否,但是实现形式是不同的。
v-if是对dom的操作,是在增删dom节点,v-show只是操作
<h2 style="display: none;">Hello Vue</h2>
为元素设置一下内联样式而已
当页面元素显示与隐藏频繁切换的时候,建议使用v-show,如果是一次性操作,则选择使用v-if
列表渲染
v-for
<div id=app>
<ul>
<li v-for="item in names">{{item}}</li>
</ul>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
names: ['中国', '美国', '英国']
}
})
</script>
- 获取索引值
<li v-for="(item,index) in names">{{index}} - {{item}}</li>
- 遍历对象
<div id=app>
<ul>
<li v-for="(item,key) in info">{{key}} - {{item}}</li>
</ul>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
info: {
name: 'why',
age: 18,
height: 1.88
}
}
})
</script>
key就是对象的属性名,item就是对象属性的值
:key属性
官方推荐,使用v-for的时候,为对应的元素或组件添加上一个:key属性,
为节点做一个唯一标识。
在Vue虚拟DOM的Diff算法中会用到。
可以帮助更高效的更新虚拟DOM
<li v-for="(item,index) in names" :key="item">{{key}} - {{item}}</li>
注意,不要用index作为:key
的值
事件处理
监听事件:v-on:
v-on:
可以简写为@
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
<script>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
//可以简写为greet(){......}
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
</script>
- 获取事件参数
上述案例中,我们没有传递event参数,但是function中是可以使用的
可是,如果我们需要传递普通参数,同时也要获取event,那该怎么办?
答案:greet(args,$event)
冒泡问题:.stop
修饰符
<div id=app>
<div @click="divClick">
哇哈哈
<button @click="btnClick">点我呀</button>
</div>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue'
},
methods: {
divClick() {
console.log("div被点击了");
},
btnClick() {
console.log("button被点击了");
}
}
})
</script>
在这段代码中,如果点击了button,div上的click也是会被触发的,怎么办呢?
答案:添加.stop
修饰符:<button @click.stop="btnClick">点我呀</button>
除了.stop
修饰符外,还有很多修饰符可以支持
阻止默认事件.prevent
修饰符
比如对于submit类型的input,默认是会提交表单的
<div id=app>
<form>
<input type="submit" value="提交" @click.prevent="submitClick">
</form>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue'
},
methods: {
divClick() {
console.log("div被点击了");
},
btnClick() {
console.log("button被点击了");
},
submitClick() {
console.log("点击提交");
}
}
})
</script>
用了修饰符后,就不会自动提交表单了
keyup/keydown
事件与.enter
修饰符
监听按键按下与弹起,
<div id=app>
<input type="text" @keyup="keyClickUp">
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue'
},
methods: {
divClick() {
console.log("div被点击了");
},
btnClick() {
console.log("button被点击了");
},
submitClick() {
console.log("点击提交");
},
keyClickUp() {
console.log("点击了按键");
}
}
})
</script>
如果只想监听回车键,则
<input type="text" @keyup.enter="keyClickUp">
还可以设置要监听的键的代码,如<input type="text" @keyup.13="keyClickUp">
.once
修饰符
<button @click.once="btnClick">按钮</button>
事件仅被触发一次
表单输入绑定
v-model
你可以用
v-model
指令在表单<input>
、<textarea>
及<select>
元素上创建双向数据绑定。
<div id=app>
输入:<input type="text" v-model="message">
<br/>
输出:<input type="text" :value="message">
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: ''
}
})
</script>
在输入中输入的数据,绑定到message中,message的变化,又反馈到输出中。
最终效果就是输入内容同步显示在输出中。
双向绑定,我们可以这么理解:以前都是template从Vue对象中取数据,现在可以在template中将数据绑定到Vue对象中。同时,如果message对象由于其他因素被改变,输入中也是会随之改变的。
绑定radio
<div id=app>
<!--可以省略name="sex"-->
<input type="radio" id="male" name="sex" value="男" v-model="sex">男
<input type="radio" id="female" name="sex" value="女" v-model="sex">女
<hr>
选中:{{sex}}
</div>
<script>
let app = new Vue({
el: '#app',
data: {
sex: ''
}
})
</script>
绑定checkbox
- 单选框
<div id=app>
<label>
<input type="checkbox" id="agree" v-model="isAgree">同意协议
</label>
<hr>
选中结果:{{isAgree}}
</div>
<script>
let app = new Vue({
el: '#app',
data: {
isAgree: false
}
})
</script>
- 多选框
<div id=app>
<label>
<input type="checkbox" value="足球" v-model="hobbies">足球
<input type="checkbox" value="篮球" v-model="hobbies">篮球
<input type="checkbox" value="排球" v-model="hobbies">排球
<input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球
<input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球
</label>
<hr>
选中结果:{{hobbies}}
</div>
<script>
let app = new Vue({
el: '#app',
data: {
hobbies:[]
}
})
</script>
绑定select
- 单选
<div id=app>
<label>
<select name="abc" id="" v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="榴莲">榴莲</option>
<option value="葡萄">葡萄</option>
</select>
</label>
<hr>
选中结果:{{fruit}}
</div>
<script>
let app = new Vue({
el: '#app',
data: {
fruit: '香蕉'
}
})
</script>
- 多选
<div id=app>
<label>
<select name="abc" v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="榴莲">榴莲</option>
<option value="葡萄">葡萄</option>
</select>
</label>
<hr>
选中结果:{{fruit}}
</div>
<script>
let app = new Vue({
el: '#app',
data: {
fruit: ['']
}
})
</script>
修饰符
- lazy
输入:<input type="text" v-model.lazy="message">
这样一来,就只有按回车键或光离开输入框后,才会触发绑定。
- number
<input type="number" v-model.number="age">
限定输入必须是数字
- trim
<input type="text" v-model.trim="message">
删除输入中左右两边的空格,这个就非常实用了
购物车实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id=app>
<table border="1">
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in books">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.date}}</td>
<td>{{item.price | showPrice}}</td>
<td>
<button @click="decrement(item.id)">1</button>
{{item.count}}
<button @click="increment(item.id)">+</button>
</td>
<td>
<button @click="removeThisBook(item.id)">移除</button>
</td>
</tr>
</tbody>
</table>
<h2>总价:{{totalPrice | showPrice}}</h2>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
books: [
{id: 1, name: '《算法导论》', date: '2006-9', price: 85.9, count: 1},
{id: 2, name: '《UNIX编程艺术》', date: '2006-2', price: 59, count: 1},
{id: 3, name: '《编程珠玑》', date: '2006-1', price: 39, count: 1},
{id: 4, name: '《代码大全》', date: '2006-10', price: 120, count: 1}
]
},
computed: {
//总价
totalPrice() {
//使用reduce汇总计算
return this.books.reduce((preValue, book) => {
return preValue += book.price * book.count;
}, 0);
}
},
methods: {
//删除指定id的书籍
removeThisBook(id) {
console.log("移除id:" + id);
for (let i = 0; i < this.books.length; i++) {
//获取对应id在数组中的索引
if (this.books[i].id === id) {
//删除选中的元素
this.books.splice(i, 1);
}
}
},
//加减书籍数量
decrement(id) {
//当前想要操作的书
for (let i = 0; i < this.books.length; i++) {
if (this.books[i].id === id) {
if (this.books[i].count == 0) {
alert("不能再减少了");
} else {
this.books[i].count--;
}
break;
}
}
},
increment(id) {
//当前想要操作的书
for (let i = 0; i < this.books.length; i++) {
if (this.books[i].id === id) {
this.books[i].count++;
break;
}
}
}
},
filters: {
showPrice(price) {
return "¥" + price.toFixed(2);
}
}
})
</script>
</body>
</html>
- 基本实现思路
- 页面搭建
- 数值计算:计算属性、过滤器等
- 事件函数