4.6Vue的OptionApi
1.computed计算属性的使用
在模板中可通过插值语法显示 data 中的数据。但有时需对数据转化后显示或结合多个数据显示,如对多个 data 数据运算、用三元运算符决定结果、对数据某种转化后显示。在模板中用表达式可方便实现,其初衷用于简单运算。在模板中放太多逻辑会使其过重且难维护,若多处使用还会有大量重复代码。
接着提出是否有方法将逻辑抽离出去。一种方式是将逻辑抽取到一个 method 中,放到 methods 的 options 中,但弊端是所有 data 使用过程都会变成方法调用;另一种方式是使用计算属性 computed。
官方未给出计算属性直接的概念解释,而是指出对于任何包含响应式数据的复杂逻辑,都应该使用计算属性。计算属性将混入到组件实例中,所有 getter 和 setter 的 this 上下文自动地绑定为组件实例。
计算属性的用法:
- 选项:computed
- 类型:{[key: string]: Function | { get: Function, set: Function } }
对计算属性有缓存这一现象的原因进行解释:
- 计算属性会基于它们的依赖关系进行缓存。
- 在数据不发生变化时,计算属性不需要重新计算。
- 但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算。
1.1案例练习来区别
展示三个案例及三种实现思路:
案例一:有 firstName 和 lastName 两个变量,希望拼接后在界面显示。
案例二:有分数 score,score 大于 60 时在界面显示及格,小于 60 时显示不及格。
案例三:有变量 message 记录一段文字如 Hello World,某些情况直接显示,某些情况需对文字反转。
实现思路:
思路一:在模板语法中直接使用表达式。
思路二:使用 method 对逻辑进行抽取。
思路三:使用计算属性 computed。
1.2思路一
介绍思路一(模板语法)的实现及缺点:
缺点一:模板中存在大量复杂逻辑,不便于维护(模板中表达式初衷用于简单计算)。
缺点二:当有大量一样的逻辑时,存在重复代码。
缺点三:多次使用时,很多运算也需要多次执行,没有缓存。还展示了实现思路一的代码示例:
html
预览
<!-- 1.实现思路一 --> <template id="my - app"><h2>{{ firstName + lastName }}</h2><h2>{{ score >= 60? "及格" : "不及格" }}</h2><h2>{{ message.split("").reverse().join("") }}</h2> </template>
<html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><div id="app"><h2>{{message}}</h2><!-- 1.拼接名字 --><h2>{{firstName +"" +lastName}}</h2><!-- 2.显示分数等级 --><h2>{{score>60? "及格" :"不及格"}}</h2><!-- 实现英文字符反转 --><h2>{{message.split("").reverse("").join(" ")}}</h2></div><script src="../lib/Vue.js"></script><script>const app = Vue.createApp({data:function(){return {message:"Hello Vue",firstName:"张三",lastName:"李四",score:70,}},})app.mount('#app')</script> </body> </html>
1.3思路二
内容:介绍思路二(method 实现)及其缺点。
缺点一:实际上先显示的是一个结果,但都变成了一种方法的调用。
缺点二:多次使用方法的时候,没有缓存,也需要多次计算。代码:
html
预览
<!-- 2.实现思路二 --> <template id="my - app"><h2>{{ getFullName() }}</h2><h2>{{ getResult() }}</h2><h2>{{ getReverseMessage() }}</h2> </template>
javascript
运行
methods: {getFullName() {return this.firstName + " " + this.lastName;},getResult() {return this.score >= 60? "及格" : "不及格";},getReverseMessage() {return this.message.split("").reverse().join("");} }
<html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><div id="app"><h2>{{message}}</h2><!-- 1.拼接名字 --><h2>{{method1()}}</h2><!-- 2.显示分数等级 --><h2>{{method2()}}</h2><!-- 实现英文字符反转 --><h2>{{method3()}}</h2></div><script src="../lib/Vue.js"></script><script>const app = Vue.createApp({data:function(){return {message:"Hello Vue",firstName:"张三",lastName:"李四",score:70,}},methods:{method1:function(){return this.firstName +"" +this.lastName},method2:function(){return this.score>60? "及格" :"不及格"},method3:function(){return this.message.split("").reverse("").join(" ")}}})app.mount('#app')</script> </body> </html>
1.4思路三
内容:介绍思路三(computed 实现)相关要点。注意计算属性看似是一个函数,但使用时无需加括号,后续讲 setter 和 getter 时会提及;发现无论直观还是效果上计算属性都是更好选择,且计算属性是有缓存的。
代码块:
html
预览
<!-- 3.实现思路三 --> <template id="my - app"><h2>{{ fullName }}</h2><h2>{{ result }}</h2><h2>{{ reverseMessage }}</h2> </template>
<html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><div id="app"><h2>{{message}}</h2><!-- 注意使用的时候不需要加上小括号 --><!-- 1.拼接名字 --><h2>{{computed1}}</h2><!-- 2.显示分数等级 --><h2>{{computed2}}</h2><!-- 实现英文字符反转 --><h2>{{computed3}}</h2></div><script src="../lib/Vue.js"></script><script>const app = Vue.createApp({data:function(){return {message:"Hello Vue",firstName:"张三",lastName:"李四",score:70,}},computed:{computed1:function(){return this.firstName +"" +this.lastName},computed2:function(){return this.score>60? "及格" :"不及格"},computed3:function(){return this.message.split("").reverse("").join(" ")}},})app.mount('#app')</script> </body> </html>
2.computed和method区别
3.computed的set和get
内容:计算属性在大多数情况下只需要一个 getter 方法,因此常将计算属性直接写成一个函数。但如果确实想设置计算属性的值,也可以给计算属性设置一个 setter 方法。
代码块:
javascript
运行
computed: {fullName: {get() {return this.firstName + " " + this.lastName;},set(value) {const names = value.split(" ");this.firstName = names[0];this.lastName = names[1];}} }
<html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><div id="app"><h2>{{message}}</h2><h2>{{computed1}}</h2></div><script src="../lib/Vue.js"></script><script>const app = Vue.createApp({data:function(){return {message:"Hello Vue",fistName:"zhangsan",lastName:"lisi"}},computed:{// 注意这里的写法computed1:{get(){return this.fistName+" "+this.lastName},//get()获取的值这里会传回来,注意set(value){const names = value.split(" ")this.fistName = names[0]this.lastName = names[1]}}}})app.mount('#app')</script> </body> </html>
4.侦听器watch选项的使用
- 解释什么是侦听器:开发中在 data 返回的对象中定义数据,该数据通过插值语法等绑定到 template 中,当数据变化时,template 会自动更新显示最新数据。但在某些情况下,希望在代码逻辑中监听某个数据的变化,这时就需要用侦听器 watch 来完成。
- 介绍侦听器的用法:
- 选项:watch
- 类型:{[key: string]: string | Function | Object | Array }
我们先来看一个例子:
- 当我们点击按钮的时候会修改 info.name 的值;
- 这个时候我们使用 watch 来侦听 info,是侦听不到的。这是因为默认情况下,watch 只是在侦听 info 的引用变化,对于内部属性的变化是不会做出响应;
- 这个时候我们可以使用一个选项 deep 进行更深层的侦听;
- 注意前面我们说过 watch 里面侦听的属性对应的也可以是一个 Object;
还有另外一个属性,是希望一开始的就会立即执行一次:- 这个时候我们使用 immediate 选项;
- 这个时候无论后面数据是否有变化,侦听的函数都会有执行一次;
代码在下一页课件中。
watch: {info: {handler(newValue, oldValue) {console.log(newValue, oldValue);},deep: true,immediate: true},'info.name': function(newValue, oldValue) {console.log(newValue, oldValue);} }
<html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><div id="app"><h2>{{changeMessage()}}</h2><h2>{{changeInfo()}}</h2></div><script src="../lib/Vue.js"></script><script>const app = Vue.createApp({data:function(){return {message:"Hello Vue",info:{name:"张三", age: 18}}},methods:{changeMessage: function(){this.message = "你好啊,界"return this.message},changeInfo: function(){this.info = {name:"Kobe"}return this.info}},watch:{//默认有两个参数:newValue/oldValuemessage: function(newValue,oldValue){console.log("message的数据发生了变化",newValue,oldValue)},info:function(newValue,oldValue){//2.如果是对象类型,那么拿到的是代理对象//console.log("message的数据发生了变化",newValue,oldValue)//3.获取原生对象console.log(Vue.toRaw(newValue))}}})app.mount('#app')</script> </body> </html>
Vue的watch侦听选项
<html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><div id="app"><h2>{{changeMessage()}}</h2><h2>{{changeInfo()}}</h2></div><script src="../lib/Vue.js"></script><script>const app = Vue.createApp({data:function(){return {message:"Hello Vue",info:{name:"张三", age: 18}}},methods:{changeMessage: function(){this.message = "你好啊,界"return this.message},changeInfo: function(){this.info = {name:"Kobe"}return this.info}},watch:{//默认有两个参数:newValue/oldValuemessage: function(newValue,oldValue){console.log("message的数据发生了变化",newValue,oldValue)},deep:true,//当点击按钮发生变化的时候,也能够深度的监听到immediate:true,//只是对第一次的监听有效info:function(newValue,oldValue){//2.如果是对象类型,那么拿到的是代理对象//console.log("message的数据发生了变化",newValue,oldValue)//3.获取原生对象console.log(Vue.toRaw(newValue))}}})app.mount('#app')</script> </body> </html>
5.侦听器watch其他写法
- 提到 Vue3 文档未提及,但 Vue 文档有提到的侦听对象属性的方式:
'info.name': function(newValue, oldValue) { console.log(newValue, oldValue); }
。- 介绍使用
$watch
API 的方式:在created
生命周期中,使用this.$watch
来侦听。第一个参数是要侦听的源;第二个参数是侦听的回调函数callback
;第三个参数是额外的其他选项,比如deep
、immediate
。代码块:
javascript
运行
// 侦听对象属性方式 'info.name': function(newValue, oldValue) {console.log(newValue, oldValue); } // 使用$watch API方式 created() {this.$watch('message', (newValue, oldValue) => {console.log(newValue, oldValue);}, {deep: true, immediate: true}) }
6.阶段性综合案例练习
<html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>table{border-collapse: collapse;text-align: center;}thead{background-color: #f5f5f5;}th,td{border: 1px solid #aaa;padding: 8px 16px;}.active{background-color: pink;}</style> </head> <body><div id="app"><template v-if="books.length"><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" :key="item.id"@click="myclick(index)":class="{active:index===indexClick }"><td>{{index+1}}</td><td>{{item.name}}</td><td>{{item.date}}</td><td>{{fomatPrice(item.price)}}</td><td><!-- 实现禁用功能 --><button :disabled="item.count<=0" @click="decrement(index,item)">-</button>{{item.count}}<button @click="increment(index,item)">+</button></td><td><button @click="myremove(index)">移除</button></td></tr></tbody></table><h1>总价{{fomatPrice(totalPrice)}}</h1></template><template v-else><h1>购物车为空,请添加你喜欢的数据吧</h1></template></div><script src="./data.js"></script><script src="../lib/Vue.js"></script><script>const app = Vue.createApp({data:function(){return {message:"Hello Vue",books:books,indexClick:-1}},computed:{totalPrice(){return this.books.reduce((preValue,item)=>{console.log(preValue,item)return preValue + item.price * item.count},0)}},methods:{fomatPrice:function(value){return "¥"+value},decrement:function(index, item){// item.count++this.books[index].count--},increment: function(index,item){//item.count++this.books[index].count++},myremove:function(index){this.books.splice(index,1)},myclick: function(index){this.indexClick=index}}})app.mount('#app')</script> </body> </html>