一、指令补充
1. 指令修饰符
通过 “.”指明一些指令后缀,不同后缀封装了不同的处理操作 -> 简化代码
键盘按键修饰符
如:keyup.enter -> 键盘回车监听
别名修饰符 | 键值修饰符 | 对应按键 |
.delete | .8/.46 | 回格 / 删除 |
.tab | .9 | 制表 |
.enter | .13 | 回车 |
.esc | .27 | 退出 |
.space | .32 | 空格 |
.left | .37 | 左 |
.up | .38 | 上 |
.right | .39 | 右 |
.down | .40 | 下 |
示例1:Day01中的《小黑记事本》案列,实现在输入框回车即添加任务
示例2:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h3>@keyup.enter → 监听键盘回车事件</h3>
<input v-model="username" type="text" @keyup.enter="fn()">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: ''
},
methods: {
fn(e) {
console.log('键盘回车的时候触发', this.username)
}
}
})
</script>
</body>
</html>
效果:
鼠标按键修饰符
如:@click.left -> 左键监听
修饰符 | 可用版本 | 对应按键 |
.left | 2.2.0以上 | 左键 |
.right | 2.2.0以上 | 右键 |
.middle | 2.2.0以上 | 中键 |
组合修饰符
如:@click.ctrl -> 监听Ctrl键
修饰符 | 可用版本 | 对应按键 |
.ctrl | 2.1.0以上 | Ctrl键 |
.alt | 2.1.0以上 | Alt键 |
.shift | 2.1.0以上 | Shift键 |
.meta | 2.1.0以上 | meta键(Windows系统键盘上的 田 键) |
v-model修饰符
修饰符 | 可用版本 | 说明 |
.lazy | 所有 | 将用户输入的数据赋值于变量的时机由输入时延迟到数据改变时 |
.number | 所有 | 自动转换用户输入为数值类型 |
.trim | 所有 | 自动过滤用户输入的首尾空白字符 |
事件修饰符
名称 | 可用版本 | 可用事件 | 说明 |
.stop | 所有 | 任意 | 当事件触发时,阻止事件冒泡 |
.prevent | 所有 | 任意 | 当事件触发时,阻止元素默认行为 |
.capture | 所有 | 任意 | 当事件触发时,阻止事件捕获 |
.self | 所有 | 任意 | 阻止事件仅作用域节点自身 |
.once | 2.14以上 | 任意 | 事件被触发一次后即解除监听 |
.passive | 2.3.0以上 | 滚动 | 移动端,限制事件永不调用preventDefault()方法 |
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.father {
width: 200px;
height: 200px;
background-color: pink;
margin-top: 20px;
}
.son {
width: 100px;
height: 100px;
background-color: skyblue;
}
</style>
</head>
<body>
<div id="app">
<h3>v-model修饰符 .trim .number</h3>
姓名:<input v-model.trim="username" type="text"><br>
年纪:<input v-model.number="age" type="text"><br>
<h3>@事件名.stop → 阻止冒泡</h3>
<!-- 冒泡是由内到外的,捕获是外到内的 -->
<div @click="fatherFn" class="father">
<div @click.stop="sonFn" class="son">儿子</div>
</div>
<h3>@事件名.prevent → 阻止默认行为</h3>
<!-- 默认点击链接会进行跳转 -->
<a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
age: '',
},
methods: {
fatherFn () {
alert('老父亲被点击了')
},
sonFn () {
alert('儿子被点击了')
}
}
})
</script>
</body>
</html>
效果:
2. v-bind对于样式操作的增强
2-1 操作class
语法::class = "对象/数组"
①对象 -> 键就是类名,值是布尔值。如果值为true,有这个类,否则没有这个类
适用场景:一个类名,来回切换
<div class="box" :class="{类名1:布尔值, 类名2:布尔值}"></div>
②数组 -> 数组中所有的类,都会添加到盒子上,本质就是一个class列表
适用场景:批量添加或删除类
<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>
示例1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 200px;
height: 200px;
border: 3px solid #000;
font-size: 30px;
margin-top: 10px;
}
.pink {
background-color: pink;
}
.big {
width: 300px;
height: 300px;
}
</style>
</head>
<body>
<div id="app">
<!-- 对象 -->
<div class="box" :class="{pink: false, big: true}">黑马程序员</div>
<!-- 数组 -->
<div class="box" :class="['pink', 'big']">黑马程序员</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
效果:
示例2:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.color-gray {color: gray}
.size-18 {font-size: 18px}
.style-italic {font-style: italic}
</style>
</head>
<body>
<div id="app">
<p class = "color-gray size-18 style-italic"> 《Vue从入门到实战》</p>
<p :class="classStr"> 《Vue从入门到实战》</p>
<p :class="classArr"> 《Vue从入门到实战》</p>
<p :class="classObj1"> 《Vue从入门到实战》</p>
<p :class="classObj2"> 《Vue从入门到实战》</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
classStr: 'color-gray size-18 style-italic', // 拼接字符串
classArr: ['color-gray', 'size-18', 'style-italic'], // 数组
classObj1: {
// 对象,绑定类名
'color-gray': true,
'size-18': true,
'style-italic': true
},
classObj2: {
// 对象,未绑定类名
'color-gray': 0,
'size-18': '',
'style-italic': false
}
}
})
</script>
</body>
</html>
效果:
扩展:当变量值为undefined、null、值为0的数字、空字符串、false 时,会被判定为假;除一般值外,[]、{}、-1、-0.1也会被判定为真。
案例:京东秒杀tab导航高亮
核心思路:①基于数据动态渲染;②准备下标记录高亮的是哪一个tab;③基于下标,动态控制class类名
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
ul {
display: flex;
border-bottom: 2px solid #e01222;
padding: 0 10px;
}
li {
width: 100px;
height: 50px;
line-height: 50px;
list-style: none;
text-align: center;
}
li a {
display: block;
text-decoration: none;
font-weight: bold;
color: #333333;
}
li a.active {
background-color: #e01222;
color: #fff;
}
</style>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item, index) in list" :key="item.id" @click="activeIndex = index">
<a :class="{active: index === activeIndex }" href="#">{{ item.name }}</a>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
activeIndex: 0, // 记录高亮
list: [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每日特价' },
{ id: 3, name: '品类秒杀' }
]
}
})
</script>
</body>
</html>
效果:
2-2 操作style
语法::style = '样式对象'
<div class="box" :style="{CSS属性名1:CSS属性值, CSS属性名2: CSS属性值}"</div>
适用场景:某个具体属性的动态设置
示例1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 200px;
height: 200px;
background-color: rgb(187, 150, 156);
}
</style>
</head>
<body>
<div id="app">
<div class="box" :style="{width: '400px', height: '400px', 'background-color': 'green'}"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
效果:
示例2:进度条
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.progress {
height: 25px;
width: 400px;
border-radius: 15px;
background-color: #272425;
border: 3px solid #272425;
box-sizing: border-box;
margin-bottom: 30px;
}
.inner {
width: 50%;
height: 20px;
border-radius: 10px;
text-align: right;
position: relative;
background-color: #409eff;
background-size: 20px 20px;
box-sizing: border-box;
transition: all 1s;
}
.inner span {
position: absolute;
right: -20px;
bottom: -25px;
}
</style>
</head>
<body>
<div id="app">
<!-- 外层盒子底色(黑色) -->
<div class="progress">
<!-- 内层盒子 —— 进度(蓝色) -->
<div class="inner" :style="{ width: percent + '%'}">
<span>{{ percent }}</span>
</div>
</div>
<button @click="percent = 25">设置25%</button>
<button @click="percent = 50">设置50%</button>
<button @click="percent = 75">设置75%</button>
<button @click="percent = 100">设置100%</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
percent: 50
}
})
</script>
</body>
</html>
效果:
示例3:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<p style="color: gray; font-size: 18px; font-style: italic;"> 《Vue从入门到实战》</p>
<p :style="styleStr">《Vue从入门到实战》</p>
<p :style="styleObj1">《Vue从入门到实战》</p>
<p :style="styleObj2">《Vue从入门到实战》</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
styleStr: 'color: gray; font-size: 18px; font-style:italic;', // 拼接字符串
styleObj1: {
// 对象,绑定样式
'color': -1 ? 'gray' : 'black',
'font-size': '18px',
'font-style': 'italic'
},
styleObj2: {
// 对象,未绑定样式
'color': 0 ? 'gray' : '',
'font-size': '' ? '18px' : '',
'font-style': null ? 'italic' : ''
}
}
})
</script>
</body>
</html>
效果:
3. v-model应用于其他表单元素
①输入框 input:text;②文本域 textarea;③复选框 input:checkbox;④单选框 input:radio;⑤下拉菜单 select
常见的表单元素都可以用v-model绑定关联 -> 快速获取 或 设置 表单元素的值
示例1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
textarea {
display: block;
width: 240px;
height: 100px;
margin: 10px 0;
}
</style>
</head>
<body>
<div id="app">
<h3>小黑学习网</h3>
<!-- 单行文本框 -->
姓名:
<input type="text" v-model="username">
<br><br>
<!-- 单个复选框 -->
是否单身:
<input type="checkbox" v-model="isSingle">
<br><br>
<!--
前置理解:
1. name: 给单选框加上 name 属性 可以分组 → 同一组互相会互斥
2. value: 给单选框加上 value 属性,用于提交给后台的数据
结合 Vue 使用 → v-model
-->
性别:
<input v-model="gender" type="radio" name="gender" value="1">男
<input v-model="gender" type="radio" name="gender" value="2">女
<br><br>
<!--
前置理解:
1. option 需要设置 value 值,提交给后台
2. select 的 value 值,关联了选中的 option 的 value 值
结合 Vue 使用 → v-model
-->
所在城市:
<select v-model="cityId">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">成都</option>
<option value="104">南京</option>
</select>
<br><br>
自我描述:
<textarea v-model="desc"></textarea>
<button>立即注册</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
isSingle: false,
gender: '1',
cityId: '101',
desc: ''
}
})
</script>
</body>
</html>
效果:
示例2:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h3>单行文本框</h3>
<input type="text" v-model="singleText" style="width: 240px;">
<p> {{ singleText }}</p>
<h3>多行文本框</h3>
<textarea v-model="multiText" style="width: 240px;"></textarea>
<pre> {{ multiText }}</pre>
<h3>单选框</h3>
<!-- 由于点击被选中的单选项无法取消其被选中状态,所以实战中一般没有使用单个单选项的场景
这里,设置v-model共用一个变量(radioValue)可实现RadioGroup的效果
-->
<input id="ra" type="radio" value="杨玉环" v-model="radioValue">
<label for="ra">A.杨玉环</label>
<input id="rb" type="radio" value="赵飞燕" v-model="radioValue">
<label for="rb">B.赵飞燕</label>
<p>{{ radioValue }}</p>
<h3>单个复选框</h3>
<!-- 单个复选框被用于true和false的切换 -->
<input id="c" type="checkbox" v-model="toggleValue">
<label for="c">天生丽质</label>
<p>{{ toggleValue }}</p>
<h3>多个复选框</h3>
<!--多个复选框,v-model接收数组类型变量-->
<input id="ca" type="checkbox" value="漂亮" v-model="checkedValues">
<label for="ca">A.回眸一笑百媚生</label>
<input id="cb" type="checkbox" value="瘦弱" v-model="checkedValues">
<label for="cb">B.体轻能为掌上舞</label>
<input id="cc" type="checkbox" value="得宠" v-model="checkedValues">
<label for="cc">C.三千宠爱在一身</label>
<p>{{ checkedValues.join(', ') }}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
singleText: '',
multiText: '',
radioValue: '',
toggleValue: false,
checkedValues: []
}
})
</script>
</body>
</html>
效果:
二、computed计算属性
1. 基础语法
概念:基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算
语法:
①声明在computed配置项中,一个计算属性对应一个函数;
②使用起来和普通属性一样使用 {{ 计算属性名 }}
计算属性默认的简写,只能读取访问,不能“修改”。
computed: {
计算属性名 () {
基于现有数据,编写求值逻辑
return 结果
}
}
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
border: 1px solid #000;
text-align: center;
width: 240px;
}
th,td {
border: 1px solid #000;
}
h3 {
position: relative;
}
</style>
</head>
<body>
<div id="app">
<h3>小黑的礼物清单</h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<!-- 目标:统计求和,求得礼物总数 -->
<p>礼物总数:{{ totalCount }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 现有的数据
list: [
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 3 },
{ id: 3, name: '铅笔', num: 5 },
]
},
computed: {
totalCount() {
// 基于现有的数据,编写求值逻辑
// 计算属性函数内部,可以直接通过this访问到app实例
// 需求:对this.list数组里面的num进行求和 -> reduce
// sum是累加器,初始值为0,item是数组中的当前元素.将累加器和当前元素的num属性相加
return this.list.reduce((sum, item) => sum + item.num, 0)
}
}
})
</script>
</body>
</html>
效果:
2. 计算属性 vs 方法
computed计算属性
作用:封装了一段对于数据的处理,求得一个结果。
语法:
①写在computed配置项中;
②作为属性,直接使用 -> this.计算属性 {{ 计算属性 }}
缓存特性(提升性能):计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算 -> 并再次缓存
methods方法
作用:给实例提供一个方法,调用以处理业务逻辑。
语法:
①写在methods配置项中;
②作为方法,需要调用 -> this.方法名() {{ 方法名() }} @事件名="方法名"
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
border: 1px solid #000;
text-align: center;
width: 300px;
}
th,td {
border: 1px solid #000;
}
h3 {
position: relative;
}
span {
position: absolute;
left: 145px;
top: -4px;
width: 16px;
height: 16px;
color: white;
font-size: 12px;
text-align: center;
border-radius: 50%;
background-color: #e63f32;
}
</style>
</head>
<body>
<div id="app">
<h3>小黑的礼物清单🛒<span>{{ totalCount }}</span></h3>
<h3>小黑的礼物清单🛒<span>{{ totalCount }}</span></h3>
<h3>小黑的礼物清单🛒<span>{{ totalCount }}</span></h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<p>礼物总数:{{ totalCount }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 现有的数据
list: [
{ id: 1, name: '篮球', num: 3 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
methods: {
totalCountFn () {
console.log('methods方法执行了')
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
},
computed: {
// 计算属性:有缓存,一旦计算出来结果,就会立即缓存
// 下一次读取 -> 直接读取缓存即可
// 如果数据发生变化,会自动重新计算,并缓存结果
totalCount () {
console.log('计算属性执行了')
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
})
</script>
</body>
</html>
computed计算属性:调用4次,只计算1次
methods方法:调用4次,计算4次
3. 完整写法
计算属性默认的简写,只能读取访问,不能“修改”
如果要“修改”,需要写计算属性的完整写法。
语法:
computed: {
计算属性名: {
get() {
一段代码逻辑(计算逻辑)
return 结果
},
set(修改的值) {
一段代码逻辑(修改逻辑)
}
}
}
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
<p>姓名:{{ fullName }}</p>
<button @click="changeName">修改姓名</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '',
lastName: ''
},
computed: {
// 简写 -> 获取,没有配置 设置 的逻辑
// fullName() {
// return this.firstName + this.lastName
// }
// 完整写法 -> 获取 + 设置
fullName: {
// 当fullName计算属性,被获取求值时,执行get(有缓存读缓存)
// 会将返回值作为求值的结果
get() {
return this.firstName + this.lastName
},
// 当fullName计算属性,被修改赋值时,执行set方法
// 修改的值,传递给set方法的形参
set(value) {
console.log(value)
this.firstName = value.slice(0, 1)
this.lastName = value.slice(1)
}
}
},
methods: {
changeName() {
this.fullName = '吕小布'
}
}
})
</script>
</body>
</html>
效果:
4. 成绩案列
需求说明:①渲染功能;②删除功能;③添加功能;④统计总分,求平均分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles/index.css" />
<title>Document</title>
</head>
<body>
<div id="app" class="score-case">
<div class="table">
<table>
<thead>
<tr>
<th>编号</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody v-if="list.length > 0">
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.subject }}</td>
<!-- 小于60分的标红 -->
<td :class="{ red: item.score < 60 }">{{ item.score }}</td>
<td><a @click.prevent="del(item.id)" href="#">删除</a></td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="5">
<span class="none">暂无数据</span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<span>总分:{{ totalScore }}</span>
<span style="margin-left: 50px">平均分:{{ aveScore }}</span>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="form">
<div class="form-item">
<div class="label">科目:</div>
<div class="input">
<input
type="text"
placeholder="请输入科目"
v-model.trim="subject"
/>
</div>
</div>
<div class="form-item">
<div class="label">分数:</div>
<div class="input">
<input
type="text"
placeholder="请输入分数"
v-model.number="score"
/>
</div>
</div>
<div class="form-item">
<div class="label"></div>
<div class="input">
<button class="submit" @click="add">添加</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [
{ id: 1, subject: '语文', score: 20 },
{ id: 7, subject: '数学', score: 99 },
{ id: 12, subject: '英语', score: 70 },
],
subject: '',
score: ''
},
methods: {
del(id) {
this.list = this.list.filter(res => res.id !== id)
},
add() {
if(!this.subject) {
alert('请输入科目!')
return
}
if(this.score > 100 || this.score < 0 || typeof this.score !== 'number') {
alert('请输入正确的成绩!')
return
}
this.list.unshift({
id: +new Date(),
subject: this.subject,
score: this.score
})
this.subject = ''
this.score = ''
}
},
computed: {
totalScore() {
return this.list.reduce((sum, item) => sum + item.score, 0)
},
aveScore() {
if(this.list.length === 0) {
return 0
}
return (this.totalScore / this.list.length).toFixed(2)
}
}
})
</script>
</body>
</html>
效果:
三、watch侦听器(监视器)
作用:监视数据变化,执行一些业务逻辑 或 异步操作。
语法:
①简单写法:简单类型数据,直接监视
data: {
words: '苹果',
obj: {
words: '苹果'
}
},
watch: {
// 该方法会在数据变化时,触发执行
数据属性名(newValue, odlValue) {
一些业务逻辑或异步操作
},
'对象.属性名'(newValue, oldValue) {
一些业务逻辑或异步操作
}
}
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-size: 18px;
}
#app {
padding: 10px 20px;
}
.query {
margin: 10px 0;
}
.box {
display: flex;
}
textarea {
width: 300px;
height: 160px;
font-size: 18px;
border: 1px solid #dedede;
outline: none;
resize: none;
padding: 10px;
}
textarea:hover {
border: 1px solid #1589f5;
}
.transbox {
width: 300px;
height: 160px;
background-color: #f0f0f0;
padding: 10px;
border: none;
}
.tip-box {
width: 300px;
height: 25px;
line-height: 25px;
display: flex;
}
.tip-box span {
flex: 1;
text-align: center;
}
.query span {
font-size: 18px;
}
.input-wrap {
position: relative;
}
.input-wrap span {
position: absolute;
right: 15px;
bottom: 15px;
font-size: 12px;
}
.input-wrap i {
font-size: 20px;
font-style: normal;
}
</style>
</head>
<body>
<div id="app">
<!-- 条件选择框 -->
<div class="query">
<span>翻译成的语言:</span>
<select>
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
</div>
<!-- 翻译框 -->
<div class="box">
<div class="input-wrap">
<!-- <textarea v-model="words"></textarea> -->
<textarea v-model="obj.words"></textarea>
<span><i>⌨️</i>文档翻译</span>
</div>
<div class="output-wrap">
<div class="transbox">{{ result }}</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script src="./axios.js"></script>
<script>
// 接口地址:https://applet-base-api-t.itheima.net/api/translate
// 请求方式:get
// 请求参数:
// (1)words:需要被翻译的文本(必传)
// (2)lang: 需要被翻译成的语言(可选)默认值-意大利
// -----------------------------------------------
const app = new Vue({
el: '#app',
data: {
words: '',
obj: {
words: ''
},
result: '', // 翻译结果
// timer: null // 延时器id
},
// 具体讲解:(1) watch语法 (2) 具体业务实现
watch: {
// 改方法会在数据变化时执行
// newValue:变化后的新值, oldValue:变化前的旧值(一般不用)
// words(newValue) {
// console.log('变化了', newValue)
// }
'obj.words' (newValue) {
// console.log('变化了', newValue)
// 防抖:延迟执行
// 如果一段时间内触发了,清空计时器
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios({
url: 'https://applet-base-api-t.itheima.net/api/translate',
method: 'GET',
params: {
words: newValue
}
})
// console.log(res)
this.result = res.data.data
}, 200)
}
}
})
</script>
</body>
</html>
效果:
②完整写法:添加额外配置项
(1)deep: true 对复杂类型深度监视
(2)immediate: true 初始化立刻执行一次handler方法
(3)handler: 要执行的方法
data: {
obj: {
words: '苹果',
lang: 'italy'
},
},
watch: {
// watch完整写法
数据属性名: {
deep: true, // 深度监视
immediate: true,// 是否立刻执行一次handler方法
handler(newValue) {
console.log(newValue)
}
}
}
示例:
要求:输入内容,修改语言,都实时翻译
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-size: 18px;
}
#app {
padding: 10px 20px;
}
.query {
margin: 10px 0;
}
.box {
display: flex;
}
textarea {
width: 300px;
height: 160px;
font-size: 18px;
border: 1px solid #dedede;
outline: none;
resize: none;
padding: 10px;
}
textarea:hover {
border: 1px solid #1589f5;
}
.transbox {
width: 300px;
height: 160px;
background-color: #f0f0f0;
padding: 10px;
border: none;
}
.tip-box {
width: 300px;
height: 25px;
line-height: 25px;
display: flex;
}
.tip-box span {
flex: 1;
text-align: center;
}
.query span {
font-size: 18px;
}
.input-wrap {
position: relative;
}
.input-wrap span {
position: absolute;
right: 15px;
bottom: 15px;
font-size: 12px;
}
.input-wrap i {
font-size: 20px;
font-style: normal;
}
</style>
</head>
<body>
<div id="app">
<!-- 条件选择框 -->
<div class="query">
<span>翻译成的语言:</span>
<select v-model="obj.lang">
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
</div>
<!-- 翻译框 -->
<div class="box">
<div class="input-wrap">
<textarea v-model="obj.words"></textarea>
<span><i>⌨️</i>文档翻译</span>
</div>
<div class="output-wrap">
<div class="transbox">{{ result }}</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script src="./axios.js"></script>
<script>
// 接口地址:https://applet-base-api-t.itheima.net/api/translate
// 请求方式:get
// 请求参数:
// (1)words:需要被翻译的文本(必传)
// (2)lang: 需要被翻译成的语言(可选)默认值-意大利
// -----------------------------------------------
const app = new Vue({
el: '#app',
data: {
obj: {
words: '小黑',
lang: 'italy'
},
result: '', // 翻译结果
},
// 具体讲解:(1) watch语法 (2) 具体业务实现
watch: {
obj: {
deep: true, // 深度观察,对象任何层级数据发生变化,watch方法都会被触发
immediate: true, // 立即调用:在侦听开始时立即调用一次watch方法
handler(newValue) {
// console.log('对象被修改了', newValue)
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios({
url: 'https://applet-base-api-t.itheima.net/api/translate',
method: 'GET',
params: newValue
})
this.result = res.data.data
}, 200)
}
}
}
})
</script>
</body>
</html>
效果:
四、综合案列 — 水果购物车
需求明确:
①渲染功能:v-if/v-else v-for :class;
②删除功能:点击传参 filter过滤覆盖原数组;
③修改数量:点击传参 find找对象;
④全选反选:计算属性computed 完整写法get/set;
⑤统计选中的总价 和 总数量:计算属性computed reduce条件求和;
⑥持久化到本地:watch监视,localStorage,JSON.stringfy,JSON.parse
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/inputnumber.css" />
<link rel="stylesheet" href="./css/index.css" />
<title>购物车</title>
</head>
<body>
<div class="app-container" id="app">
<!-- 顶部banner -->
<div class="banner-box"><img src="./img/fruit.jpg" alt="" /></div>
<!-- 面包屑 -->
<div class="breadcrumb">
<span>🏠</span>
/
<span>购物车</span>
</div>
<!-- 购物车主体 -->
<div class="main" v-if="fruitList.length > 0">
<div class="table">
<!-- 头部 -->
<div class="thead">
<div class="tr">
<div class="th">选中</div>
<div class="th th-pic">图片</div>
<div class="th">单价</div>
<div class="th num-th">个数</div>
<div class="th">小计</div>
<div class="th">操作</div>
</div>
</div>
<!-- 身体 -->
<div class="tbody">
<div class="tr" v-for="(item, index) in fruitList" :key="item.id" :class="{active: item.isChecked}">
<div class="td"><input type="checkbox" v-model="item.isChecked" /></div>
<div class="td"><img :src="item.icon" alt="" /></div>
<div class="td">{{ item.price }}</div>
<div class="td">
<div class="my-input-number">
<button :disabled="item.num <= 1" class="decrease" @click="sub(item.id)"> - </button>
<span class="my-input__inner">{{ item.num }}</span>
<button class="increase" @click="add(item.id)"> + </button>
</div>
</div>
<div class="td">{{ item.price * item.num}}</div>
<div class="td" @click="del(item.id)"><button>删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<input type="checkbox" v-model="isAll"/>
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价 : ¥ <span class="price" >{{ totalPrice }}</span></span>
<!-- 结算按钮 -->
<button class="pay" >结算( {{ totalCount}} )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div class="empty" v-else>🛒空空如也</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
// 默认值
const defaultArr = [
{
id: 1,
icon: './img/火龙果.png',
isChecked: true,
num: 2,
price: 6,
},
{
id: 2,
icon: './img/荔枝.png',
isChecked: false,
num: 7,
price: 20,
},
{
id: 3,
icon: './img/榴莲.png',
isChecked: false,
num: 3,
price: 40,
},
{
id: 4,
icon: './img/鸭梨.png',
isChecked: true,
num: 10,
price: 3,
},
{
id: 5,
icon: './img/樱桃.png',
isChecked: false,
num: 20,
price: 34,
},
]
const app = new Vue({
el: '#app',
data: {
// 水果列表
// 从本地读取
fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr
},
computed: {
// isAll() {
// // 必须所有的小选框都选中,全选按钮才选中 -> every
// return this.fruitList.every(item => item.isChecked === true)
// }
isAll: {
set(value) {
// console.log(value)
// 基于拿到的布尔值,要让所有的小选框都同步状态
this.fruitList.forEach(item => item.isChecked = value)
},
get() {
return this.fruitList.every(item => item.isChecked === true)
}
},
// 统计选中的总数
totalCount() {
return this.fruitList.reduce((sum, item) => {
if(item.isChecked) {
// 选中 -> 需要累加
return sum + item.num
} else {
// 没选中 -> 不需要累加
return sum
}
}, 0)
},
// 统计选中的总价
totalPrice() {
return this.fruitList.reduce((sum, item) => {
if(item.isChecked) {
// 选中 -> 需要累加
return sum + item.num * item.price
} else {
// 没选中 -> 不需要累加
return sum
}
}, 0)
}
},
methods: {
del(id) {
this.fruitList = this.fruitList.filter(res => res.id !== id)
},
add(id) {
const fruit = this.fruitList.find(item => item.id === id)
fruit.num++
},
sub(id) {
const fruit = this.fruitList.find(item => item.id === id)
fruit.num--
}
},
watch: {
fruitList: {
deep: true,
handler(newValue) {
// console.log(newValue)
// 需要将变化后的newValue存入本地(转JSON)
localStorage.setItem('list', JSON.stringify(newValue))
}
}
}
})
</script>
</body>
</html>
五、补充知识
名称 | 说明 |
push | 将一个或多个元素添加至数组末尾,并返回新数组的长度 |
pop | 从数组中删除并返回最后一个元素 |
shift | 从数组中删除并返回第一个元素 |
unshift | 将一个或多个元素添加至数组开头,并返回新数组的长度 |
splice | 从数组中删除元素或向数组添加元素 |
sort | 对数组元素排序,默认按照Unicode编码排序,返回排序后的数组 |
reverse | 将数组中的元素位置颠倒,返回颠倒后的数组 |
reduce | 计算数组中每个元素中某项的和 |
filter | 对数组元素进行过滤,仅保留符合条件的项(不会改变原数组的值) |