Vue基础入门到实战2(指令补充+计算属性+侦听器)

1 指令修饰符

1.1 什么是指令修饰符?

所谓指令修饰符就是通过"."指明一些指令 后缀。不同的后缀封装了不同的处理操作 —> 简化代码

1.2 按键修饰符

  • @keyup.enter —>当点击enter键的时候才触发

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<h3>@keyup.enter → 监听键盘回车事件</h3>
<input @keyup.enter="fn" v-model="username" type="text">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: ''
},
methods: {
fn (e) {
console.log('键盘回车的时候触发', this.username)
}
}
})
</script>

1.3 v-model 修饰符

  • v-model.trim —>去除首位空格
  • v-model.number —>转数字

1.4 事件修饰符

  • @事件名.stop —> 阻止冒泡
  • @事件名.prevent —>阻止默认行为
  • @事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为

2 v-bind样式控制增强-操作class

为了方便开发者进行样式控制, Vue 扩展了 v-bind 的语法,可以针对 class 类名style 行内样式 进行控制 。

2.1 语法

1
<div> :class = "对象/数组">这是一个div</div>

2.2 对象语法

当class动态绑定的是 对象 时,键就是类名,值就是布尔值 ,如果值是 true,就有这个类,否则没有这个类。这个使用的时候,直接输入类名即可。

1
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>

适用场景:一个类名,来回切换

2.3 数组语法

当class动态绑定的是 数组 时 → 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表。这个在使用时,里面的类名必须使用单引号包裹。

1
<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>

使用场景:批量添加或删除类

2.4 代码练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 <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>


<div id="app">
<!--绑定对象-->
<div class="box" :class="{pink:true, big:false}">黑马程序员</div>
<!--绑定数组-->
<div class="box" :class="['pink', 'big']">黑马程序员</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {

}
})
</script>

3 京东秒杀-tab栏切换导航高亮

3.1 需求

当我们点击哪个tab页签时,哪个tab页签就高亮

3.2 准备代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 <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>

<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/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每日特价' },
{ id: 3, name: '品类秒杀' }
]
}
})
</script>

3.3.思路

1.基于数据,动态渲染tab(v-for)

2.准备一个下标 记录高亮的是哪一个 tab

3.基于下标动态切换 class 的类名

4 v-bind对有样式控制的增强-操作style

4.1 语法

1
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>

4.2 代码练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
.box {
width: 200px;
height: 200px;
background-color: rgb(187, 150, 156);
}
</style>
<div id="app">
<div class="box" :style="{width: 400px, height: 400px, background-color}: rgb(25, 25, 25)"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {

}
})
</script>

4.3 进度条案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<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>

<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/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
percent: 30
}
})
</script>

5 v-model在其他表单元素的使用

5.1 讲解内容

常见的表单元素都可以用 v-model 绑定关联 → 快速 获取设置 表单元素的值

它会根据 控件类型 自动选取 正确的方法 来更新元素

1
2
3
4
5
6
输入框  input:text   ——> value
文本域 textarea ——> value
复选框 input:checkbox ——> checked
单选框 input:radio ——> checked
下拉菜单 select ——> value
...

5.2 代码准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 <style>
textarea {
display: block;
width: 240px;
height: 100px;
margin: 10px 0;
}
</style>
<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/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
isSingle: false,
gender: "2",
cityId: '102',
desc: ""
}
})
</script>

6 computed计算属性

6.1 概念

基于 现有的数据,计算出来的 新属性依赖 的数据变化,自动 重新计算。

6.2 语法

  1. 声明在 computed 配置项 中,一个计算属性对应一个函数
  2. 使用起来和普通属性一样使用 {{计算属性名}}

6.3 注意

  1. computed 配置项和 data 配置项是 同级
  2. computed 中的计算属性 虽然是函数的写法,但他 依然是个属性
  3. computed 中的计算属性 不能 和 data 中的属性 同名
  4. 使用 computed 中的计算属性和使用 data 中的属性是一样的用法
  5. computed 中计算属性内部的 this 依然 指向的是Vue实例

6.4 案例

比如我们可以使用计算属性实现下面这个业务场景。根据原有的礼物数量计算出总的礼物总数,礼物总数依赖于上面的礼物清单。

68203932785

6.5 代码准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<style>
table {
border: 1px solid #000;
text-align: center;
width: 240px;
}
th,td {
border: 1px solid #000;
}
h3 {
position: relative;
}
</style>

<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/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 现有的数据
list: [
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
computed: {
totalCount () {
// 基于现有的数据,编写求值逻辑
// 计算属性函数内部,可以直接通过 this 访问到 app 实例
// console.log(this.list)

// 需求:对 this.list 数组里面的 num 进行求和 → reduce
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
})
</script>

7 computed计算属性 VS methods方法

7.1 computed计算属性

作用:封装了一段对于 数据 的处理,求得一个 结果

语法:

  1. 写在 computed 配置项中
  2. 作为属性,直接使用
    • js 中使用计算属性: this.计算属性
    • 模板中使用计算属性:{{计算属性}}

7.2 methods计算属性

作用:给 Vue 实例提供一个 方法,调用以 处理业务逻辑

语法:

  1. 写在 methods 配置项中
  2. 作为方法调用
    • js 中调用:this.方法名()
    • 模板中调用 {{方法名()}} 或者 @事件名=“方法名”

7.3 计算属性的优势

  1. 缓存特性(提升性能)

    计算属性会对计算出来的结果缓存,再次使用直接读取缓存,

    依赖项变化了,会自动重新计算 → 并再次缓存

  2. methods 没有缓存特性

7.4 总结

1.computed 有缓存特性,methods 没有缓存

2.当一个结果依赖其他多个值时,推荐使用计算属性.

3.当处理业务逻辑时,推荐使用methods方法,比如事件的处理函数.

8 计算属性的完整写法

既然计算属性也是属性,能访问,应该也能修改了?

  1. 计算属性默认的简写,只能读取访问,不能 “修改”
  2. 如果要 “修改” → 需要写计算属性的完整写法

68204182296

完整写法代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<div id="app">
姓:<input type="text" v-model="firstName"> +
名:<input type="text" v-model="lastName"> =
<p>姓名:{{ fullName }}</p>
<br><br>
<button>改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '刘',
lastName: '备'
},
computed: {
// 完整写法 → 获取 + 设置
fullName: {
// (1) 当fullName计算属性,被获取求值时,执行get(有缓存,优先读缓存)
// 会将返回值作为,求值的结果
get () {
return this.firstName + this.lastName
},
// (2) 当fullName计算属性,被修改赋值时,执行set
// 修改的值,传递给set方法的形参
set (value) {
// console.log(value.slice(0, 1))
// console.log(value.slice(1))
this.firstName = value.slice(0, 1)
this.lastName = value.slice(1)
}
}
},
methods: {
changeName () {
this.fullName = '黄忠'
}
}
})
</script>

9 watch侦听器(监视器)

9.1 作用

监视数据变化,执行一些业务逻辑或异步操作

9.2 语法

  1. watch 同样声明在跟 data 同级的配置项中

  2. 简单写法: 简单类型数据直接监视

  3. 完整写法:添加额外配置项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    data: { 
      words: '苹果',
      obj: {
        words: '苹果'
      }
    },

    watch: {
    // 该方法会在数据变化时,触发执行
      数据属性名 (newValue, oldValue) {
    一些业务逻辑 或 异步操作。
    },
    '对象.属性名' (newValue, oldValue) {
    一些业务逻辑 或 异步操作。
    }
    }

9.3 翻译案例-代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<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',
params: {
words: newValue
}
})
this.result = res.data.data
console.log(res.data.data)
}, 300)
}
}
})
</script>

9.4 完整写法

完整写法 —>添加额外的配置项

  1. deep:true 对复杂类型进行 深度监听
  2. immdiate:true 初始化 立刻执行一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data: {
  obj: {
    words: '苹果',
    lang: 'italy'
  },
},

watch: {// watch 完整写法
  对象: {
deep: true, // 深度监视
immdiate:true,//立即执行handler函数
    handler (newValue) {
      console.log(newValue)
    }
  }
}

10 综合案例

购物车案例

68205100897

需求说明

  1. 渲染功能
  2. 删除功能
  3. 修改个数
  4. 全选反选
  5. 统计 选中的 总价 和 总数量
  6. 持久化到本地

实现思路

  1. 基本渲染: v-for 遍历、:class 动态绑定样式

  2. 删除功能 : v-on 绑定事件,获取当前行的 id

  3. 修改个数 : v-on绑定事件,获取当前行的 id,进行筛选出对应的项然后增加或减少

  4. 全选反选

    1. 必须所有的小选框都选中,全选按钮才选中 → every
    2. 如果全选按钮选中,则所有小选框都选中
    3. 如果全选取消,则所有小选框都取消选中
    4. 声明计算属性,判断数组中的每一个 checked 属性的值,看是否需要全部选
  5. 统计 选中的 总价 和 总数量 :通过计算属性来计算 选中的 总价和总数量

  6. 持久化到本地: 在数据变化时都要更新下本地存储 watch

代码展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
<!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">
<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" @click="activeIndex=index" :class="{active:index===activeIndex}" v-for="(item, index) in fruitList" :key="item.id">
<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="item.num--"> - </button>
<span class="my-input__inner">{{ item.num }}</span>
<button @click="item.num++" class="increase"> + </button>
</div>
</div>
<div class="td">{{ item.num * item.price }}</div>
<div class="td"><button @click="del(item.id)">删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom" v-show="fruitList.length!==0">
<!-- 全选 -->
<label class="check-all">
<input type="checkbox" v-model="isAll"/>
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">{{ totalPrice }}</span></span>
<!-- 结算按钮 -->
<button class="pay">结算( {{ totalCount }} )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div v-show="fruitList.length===0" class="empty">🛒空空如也</div>
</div>
<script src="../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: {
activeIndex: 0,
// 水果列表
fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr
},
computed:{
isAll: {
get(){
return this.fruitList.every(item=>item.isChecked)
},
set(value){
// 基于拿到的布尔值,要让所有的小选框同步
this.fruitList.forEach(item => item.isChecked=value);
}
},
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(item => item.id!== id)
}
},
watch: {
fruitList: {
deep: true,
handler (newValue){
// 需要将变化后的 newValue 存入本地 (转JSON)
localStorage.setItem('list', JSON.stringify(newValue))
}
}
}
})
</script>
</body>
</html>

Vue基础入门到实战2(指令补充+计算属性+侦听器)
https://fulequn.github.io/2024/06/Article202406042/
作者
Fulequn
发布于
2024年6月4日
许可协议