文章目录
计算属性computed
1.认识计算属性
我们知道,在模板中可以直接通过插值语法显示一些data中的数据。
但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示;
-
比如我们需要对多个data数据进行运算、三元运算符来决定结果、数据进行某种转化后显示;
-
在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算;
-
在模板中放入太多的逻辑会让模板过重和难以维护;
-
并且如果多个地方都使用到,那么会有大量重复的代码;
我们有没有什么方法可以将逻辑抽离出去呢?
-
可以,其中一种方式就是将逻辑抽取到一个method中,放到methods的options中;
-
但是,这种做法有一个直观的弊端,就是所有的data使用过程都会变成了一个方法的调用;
-
另外一种方式就是使用计算属性computed;
那么什么是计算属性呢?
-
官方并没有给出直接的概念解释;
-
而是说:对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性;
-
计算属性将被混入到组件实例中 , 所有 getter 和 setter 的 this 上下文自动地绑定为组件实例;
计算属性的用法:
-
选项:computed
-
类型:
{ [key: string]: Function | { get: Function, set: Function } }
那接下来我们通过案例来理解一下这个计算属性。
2.案例实现
我们来看三个案例:
案例一:我们有两个变量:firstName和lastName,希望它们拼接之后在界面上显示;
案例二:我们有一个分数:score
-
当score大于60的时候,在界面上显示及格;
-
score小于60的时候,在界面上显示不及格;
案例三:我们有一个变量message,记录一段文字:比如Hello World
-
某些情况下我们是直接显示这段文字;
-
某些情况下我们需要对这段文字进行反转;
我们可以有三种实现思路:
思路一:在模板语法中直接使用表达式;
思路二:使用method对逻辑进行抽取;
思路三:使用计算属性computed;
实现思路一:模板语法
思路一的实现:模板语法
-
缺点一:模板中存在大量的复杂逻辑,不便于维护(模板中表达式的初衷是用于简单的计算);
-
缺点二:当有多次一样的逻辑时,存在重复的代码;
-
缺点三:多次使用的时候,很多运算也需要多次执行,没有缓存;
<div id="app">
<!-- 1.案例一: 名字的拼接 -->
<h2>{{ firstNmae}} {{ lastName }}</h2>
<!-- 2.案例二: 根据分数显式文本 -->
<h2>{{ score >= 60? "及格": "不及格" }}</h2>
<!-- 3.案例三: 对文本的单词反转 -->
<h2>{{ message.split(" ").reverse().join(" ") }}</h2>
</div>
<!-- 从本地引入Vue -->
<script src="../js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
// 案例一的数据
firstNmae: "chen",
lastName: "yq",
// 案例二的数据
score: 50,
// 案例三的数据
message: "my name is chenyq",
};
},
});
app.mount("#app");
</script>
实现思路二:method
思路二的实现:method实现
-
缺点一:我们事实上想显示的是一个结果,但是结果都变成了一种方法的调用;
-
缺点二:多次使用方法的时候,没有缓存,也需要多次计算;
<div id="app">
<!-- 1.案例一: 名字的拼接 -->
<h2>{{ getFullName() }}</h2>
<!-- 2.案例二: 根据分数显式文本 -->
<h2>{{ getScoreLevel() }}</h2>
<!-- 3.案例三: 对文本的单词反转 -->
<h2>{{ reverseMessage() }}</h2>
</div>
<!-- 从本地引入Vue -->
<script src="../js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
// 案例一的数据
firstNmae: "chen",
lastName: "yq",
// 案例二的数据
score: 50,
// 案例三的数据
message: "my name is chenyq",
};
},
methods: {
// 案例一的方法
getFullName() {
return this.firstNmae + " " + this.lastName;
},
// 案例二的方法
getScoreLevel() {
return this.score >= 60 ? "及格" : "不及格";
},
// 案例三的方法
reverseMessage() {
return this.message.split(" ").reverse().join(" ");
},
},
});
app.mount("#app");
</script>
实现思路三:computed
思路三的实现:computed实现
-
注意:计算属性看起来像是一个函数,但是我们在使用的时候不需要加(),这个后面讲setter和getter时会讲到;
-
我们会发现无论是直观上,还是效果上计算属性都是更好的选择;
-
并且计算属性是有缓存的;
<div id="app">
<!-- 1.案例一: 名字的拼接 -->
<h2>{{ fullName }}</h2>
<!-- 2.案例二: 根据分数显式文本 -->
<h2>{{ scoreLevel }}</h2>
<!-- 3.案例三: 对文本的单词反转 -->
<h2>{{ reverseMessage }}</h2>
</div>
<!-- 从本地引入Vue -->
<script src="../js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
// 案例一的数据
firstNmae: "chen",
lastName: "yq",
// 案例二的数据
score: 50,
// 案例三的数据
message: "my name is chenyq",
};
},
computed: {
// 案例一的计算属性
fullName() {
return this.firstNmae + " " + this.lastName;
},
// 案例二的计算属性
scoreLevel() {
return this.score >= 60 ? "及格" : "不及格";
},
// 案例三的计算属性
reverseMessage() {
return this.message.split(" ").reverse().join(" ");
},
},
});
app.mount("#app");
</script>
3.计算属性 VS methods
在上面的实现思路中,我们会发现计算属性和methods的实现看起来是差别是不大的,而且我们多次提到计算属性有缓存的。
- 接下来我们来看一下同一个计算多次使用,计算属性和methods的差异:
const app = Vue.createApp({
data() {
return {
firstNmae: "chen",
lastName: "yq",
};
},
// 1.定义methods方法
methods: {
getFullName() {
console.log("调用了methods的getFullName");
return this.firstNmae + " " + this.lastName;
},
},
// 2.定义计算属性
computed: {
fullName() {
console.log("调用了computed的fullName");
return this.firstNmae + " " + this.lastName;
},
},
});
<!-- 1.使用methods -->
<h2>{{ getFullName() }}</h2>
<h2>{{ getFullName() }}</h2>
<h2>{{ getFullName() }}</h2>
<h2>{{ getFullName() }}</h2>
<!-- 2.使用computed -->
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
打印的结果如下 ( 我们会发现methods调用了四次, 而computed只会调用一次 ):
这是什么原因呢?
-
这是因为计算属性会基于它们的依赖关系进行缓存;
-
在数据不发生变化时,计算属性是不需要重新计算的;
-
但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算;
例如我们对上面代码在添加一个按钮, 要求点击按钮, 内容发生改变
<!-- 1.使用methods -->
<h2>{{ getFullName() }}</h2>
<h2>{{ getFullName() }}</h2>
<h2>{{ getFullName() }}</h2>
<h2>{{ getFullName() }}</h2>
<!-- 2.使用computed -->
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
<!-- 3.添加按钮点击改变信息 -->
<button @click="changeMessage">改变信息</button>
那么我们来看看点击按钮前后的打印效果:
- 点击按钮改变前:
- 点击按钮改变信息后:
- 改变信息后计算属性会重新执行一次, 而methods又重新执行了四次
- 由此可见, computed的性能是更高的
4.计算属性的set和get
其实上面的代码中, 我们使用的都是计算属性的简写方式
- 计算属性有完整的写法, 只不过开发中很少会这样写
- 下面给大家介绍一下计算属性的完整写法
完整写法中, 我们设置一个计算属性, 这个计算属性其实是一个对象
- 对象中有两个方法, 一个是set, 一个是get
计算属性在大多数情况下,只需要一个getter方法即可,所以我们会将计算属性直接写成一个函数。
- 计算属性的完整写法
computed: {
fullName: {
get() {
return this.firstName + " " + this.lastName;
},
},
}
- 当我们只需要getter方法的时候, 我们可以直接写出一个函数, 也就是我们之前的写法
computed: {
fullName() {
return this.firstName + " " + this.lastName;
},
}
但是,如果我们确实想设置或修改计算属性的值(了解), 基本不会使用set
- 这个时候我们也可以给计算属性设置一个setter的方法;
<div id="app">
<h2>{{ fullName }}</h2>
<button @click="setFullName">改变fullName</button>
</div>
<!-- 从本地引入Vue -->
<script src="../js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
firstName: "chen",
lastName: "yq",
};
},
computed: {
fullName: {
get() {
return this.firstName + " " + this.lastName;
},
set(value) {
const names = value.split(" ");
this.firstName = names[0];
this.lastName = names[1];
},
},
},
methods: {
setFullName() {
this.fullName = "hu tutu";
},
},
});
app.mount("#app");
</script>
监听器watch
1.认识监听器watch
什么是侦听器呢?
-
开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中;
-
当数据变化时,template会自动进行更新来显示最新的数据;
-
但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了;
侦听器的用法如下:
-
选项:watch
-
类型:
{[key: string]: string | Function | Object | Array}
演示代码, 例如监听message发生了改变
// watch监听器
watch: {
// 监听message的改变
message() {
console.log("message发生了改变");
},
}
- 如果我们想要拿到message改变之前的信息呢?
- 其实默认是有两个参数的, 一个oldValue改变之前的数据, 一个是newValue改变之后的数据
watch: {
// 默认有两个参数
message(newValue, oldValue) {
console.log("message改变前是:", oldValue);
console.log("message改变后是:", newValue);
},
}
- 如果是对象类型, 参数获取到的是Proxy代理对象
// 监听info对象
info(newValue, oldValue) {
// 如果是对象类型, 那么拿到的是代理对象
console.log("info数据发生了变化:", newValue, oldValue);
// 代理对象同样可以使用对象的属性
console.log(newValue.name, oldValue.name);
},
- 我们也可以获取Proxy代理对象的原始对象
// 监听info对象
info(newValue, oldValue) {
// 通过toRaw方法获取原始对象
console.log(Vue.toRaw(newValue), Vue.toRaw(oldValue));
},
2.watch的配置选项
我们先来看一个例子:
-
当我们点击按钮的时候会修改info.name的值;
-
这个时候我们使用watch来侦听info,可以侦听到info.name的变化吗?答案是不可以。
<div id="app">
<h2>{{ info.name }}</h2>
<button @click="changeInfo">按钮</button>
</div>
<!-- 从本地引入Vue -->
<script src="../js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
info: { name: "chenyq", age: 18 },
};
},
methods: {
changeInfo() {
this.info.name = "kaisa";
},
},
watch: {
// 监听info
info(newValue, oldValue) {
// info.name发生改变, 监听不到
console.log("info内部数据发生了变化");
},
},
});
app.mount("#app");
</script>
- 我们会发现上面案例中, 页面内容有发生改变, 而监听info对象, 无法监听到info.name的变化
这是因为默认情况下,watch只是在侦听info的引用变化,对于内部属性的变化是不会做出响应的:
-
这个时候我们可以使用一个选项deep进行更深层的侦听;
-
注意前面我们说过watch里面侦听的属性对应的也可以是一个Object;
watch: {
// 进行更深层次的监听
info: {
// 回调函数放在handler中
handler(newValue, oldValue) {
console.log("info内部数据发生了变化");
},
// 添加deep选项, 开启深度监听
deep: true,
},
},
还有另外一个属性,是希望一开始的就会立即执行一次:
-
这个时候我们使用immediate选项;
-
这个时候无论后面数据是否有变化,侦听的函数都会有限执行一次;
watch: {
// 进行更深层次的监听
info: {
// 回调函数放在handler中
handler(newValue, oldValue) {
console.log("info内部数据发生了变化");
},
// 添加deep选项, 开启深度监听
deep: true,
// 添加immediate选项, 启动默认监听
immediate: true,
},
},
3.watch的其他方式(了解)
另外一个是Vue3文档中没有提到的,但是Vue2文档中有提到的是侦听对象的属性:
- 例如刚刚监听info.name属性, 可以有如下写法:
watch: {
"info.name": function (newValue, oldValue) {
console.log("info.name发生了改变", newValue, oldValue);
},
},
还有另外一种方式就是使用 $watch 的API:我们可以在created的生命周期(暂时了解, 后续会讲到)中,使用 this.$watchs 来侦听;
- 第一个参数是要侦听的源;
- 第二个参数是侦听的回调函数callback;
- 第三个参数是额外的其他选项,比如deep、immediate;
created() {
this.$watch("info", (newValue, oldValue) => {
console.log("info内部数据发生了变化");
}, { deep: true, immediate: true})
}
综合案例
我们已经学习了很多Vue的语法, 现在我们来做一个相对综合一点的练习:书籍购物车
案例需求:
- 在界面上以表格的形式,显示一些书籍的数据;
- 点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-);
- 点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:您当前没有选择任何商品~~~);
- 在底部显示书籍的总价格;
- 点击某一行时, 某一行高亮
思路分析:
- 购买数量不能为负数, 等于零的的时候将按钮禁用:
:disabled="item.count === 0"
- 购买数量的增加和减少, 在v-for里面需要确定点击的是哪一个书籍的增加/减少, 需要在点击事件里面去传递一个index作为参数, 用于确定增加目标
- 移除同理: 需要确定具体的移除目标, 点击事件里传入index, 使用数组的方法
splice(index, 1)
进行移除 - 总价: 使用计算属性计算总价格, 遍历数组拿到每一个对象, 让数量*价格, 最后然后总价格
- 购物车为空时, 显式一行字, 用v-if 和 v-else, 判断books.lenght是否等于0
- 点击某一处高亮, 把样式写在一个类中(例如active), 动态添加类
:class = "{active: index === currentIndex}"
示例源代码:
- css源代码
<style>
* {
margin: 0;
padding: 0;
}
#app {
width: 657px;
margin: 0 auto;
}
table {
border-collapse: collapse;
}
thead {
background-color: #eee;
}
th,
td {
border: 1px solid #000;
padding: 10px 20px;
}
thead th {
color: #536270;
font-weight: 700;
}
.count {
position: relative;
text-align: center;
}
.count button {
position: absolute;
padding: 0 5px;
height: 25px;
}
.count .sub {
left: 20px;
}
.count .add {
right: 20px;
}
tbody .remove {
padding: 0 5px;
}
.active {
background-color: skyblue;
}
</style>
- html和js代码
<body>
<div id="app">
<template v-if="books.length !== 0">
<table>
<thead>
<tr>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in books"
@click="changeColor(index)"
:class="{ active: index === currentIndex }">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.date }}</td>
<td>{{ "¥" + item.price }}</td>
<td class="count">
<button
class="sub"
@click="sub(index)"
:disabled="item.count === 0">-</button>
{{ item.count }}
<button class="add" @click="add(index)">+</button>
</td>
<td>
<button class="remove" @click="remove(index)">移除</button>
</td>
</tr>
</tbody>
</table>
<h2>总价格: {{ totalPrice }}</h2>
</template>
<h2 v-else>您当前没有选择任何商品~~~</h2>
</div>
<!-- 从本地引入Vue -->
<script src="../js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
books: [
{ name: "《算法导论》", date: "2006-9", price: "85", count: "1" },
{ name: "《数据结构》", date: "2006-2", price: "59", count: "1" },
{
name: "《代码大全》",
date: "2008-10",
price: "39",
count: "1",
},
{
name: "《你不知道的JavaScript》",
date: "2006-3",
price: "128",
count: "1",
},
],
currentIndex: null
};
},
computed: {
totalPrice() {
let price = 0;
for (let item of this.books) {
price += item.price * item.count;
}
return price;
}
},
methods: {
sub(index) {
this.books[index].count--
},
add(index) {
this.books[index].count++
},
remove(index) {
this.books.splice(index, 1)
},
changeColor(index) {
this.currentIndex = index
}
}
});
app.mount("#app");
</script>
</body>
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/120090.html