Vue方法、计算属性及监听器
在vue中处理复杂的逻辑的时候,我们经常使用计算属性、方法及监听器。
methods(方法):它们是挂载在Vue对象上的函数,通常用于做事件处理函数,或自己封装的自定义函数。
computed(计算属性):在Vue中,我们可以定义一个计算属性,这个计算属性的值,可以依赖于某个data中的数据。或者说:计算属性是对数据的再加工处理。
watch(监听器):如果我们想要在数据发生改变时做一些业务处理,或者响应某个特定的变化,我们就可以通过监听器,监听数据的变化,从而做出相应的反应。
methods 方法
在使用vue的时候,可能会用到很多的方法,它们可以将功能连接到事件的指令,甚至只是创建一个小的逻辑就像其他函数一样被重用。接下来我们用方法实现上面的字符串反转。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app" > <p>原数据: {{msg}} </p> <p>新数据: {{reversedMsg()}} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { msg:'hello world!' }, methods:{ reversedMsg(){ return this.msg.split('').reverse().join(''); } } }); </script> </body> </html>
效果截图:
computed 计算属性
计算属性是根据依赖关系进行缓存的计算,并且只在需要的时候进行更新。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> <p>computed:</p> <p>原数据: {{msg}} </p> <p>新数据: {{reversedMsg}} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { msg: 'Keafmd!' }, computed: { reversedMsg() { return this.msg.split('').reverse().join(''); } } }); </script> </body> </html>
效果截图:
虽然使用computed和methods方法来实现反转,两种方法得到的结果是相同的,但本质是不一样的。
计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变的时候才会重新求值,这就意味着只要message还没有发生改变,多次访问reversedMessage计算属性立即返回的是之前计算的结果,而不会再次执行计算函数。
而对于methods方法,只要发生重新渲染,methods调用总会执行该函数。
如果某个computed需要的遍历一个极大的数组和做大量的计算,可以减小性能开销,如果不希望有缓存,则用methods。
一个案例:根据商品数量修改总价
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> <table width="100%" style="text-align: center;"> <tr> <th>商品编号</th> <th>商品名称</th> <th>商品单价</th> <th>商品数量</th> <th>合计</th> </tr> <tr> <td>1</td> <td>小米10</td> <td>{{price}}</td> <td> <button @click="subtract">-</button> {{quantity}} <button @click="add">+</button> </td> <td>{{totalPrice}}</td> </tr> </table> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { price:1599, quantity:1 }, computed:{ totalPrice(){ return this.price*this.quantity; } }, methods:{ add(){ this.quantity++; }, subtract(){ this.quantity--; } } }); </script> </body> </html>
效果截图:
此时购物数量可以低于零。
watch 监听器
watch能够监听数据的改变。监听之后会调用一个回调函数。 此回调函数的参数有两个:
更新后的值(新值)
更新前的值(旧值)
监听基本数据类型
下面使用watch来监听商品数量的变化。如果商品数量小于1,就重置成上一个值。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> <table width="100%" style="text-align: center;"> <tr> <th>商品编号</th> <th>商品名称</th> <th>商品单价</th> <th>商品数量</th> <th>合计</th> </tr> <tr> <td>1</td> <td>小米10</td> <td>{{price}}</td> <td> <button @click="subtract">-</button> {{quantity}} <button @click="add">+</button> </td> <td>{{totalPrice}}</td> </tr> </table> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { price: 2999, quantity: 1 }, computed: { totalPrice() { return this.price * this.quantity; } }, methods: { add() { this.quantity++; }, subtract() { this.quantity--; } }, watch:{ quantity(newVal,oldVal){ console.log(newVal,oldVal); this.quantity = newVal<=0?oldVal:newVal } } }); </script> </body> </html>
效果截图:
此时购物数量不能为负数。
深度监听
在上面的例子中,监听的简单的数据类型,数据改变很容易观察,但是当需要监听的数据变为对象类型的时候,上面的监听方法就失效了,因为上面的简单数据类型属于浅度监听,对应的对象类型就需要用到深度监听,只需要在上面的基础上加上deep: true就可以了。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <table width="100%" style="text-align: center;"> <tr> <th>商品编号</th> <th>商品名称</th> <th>商品单价</th> <th>商品数量</th> <th>合计</th> </tr> <tr> <td>1</td> <td>小米10</td> <td>{{goods.price}}</td> <td> <button @click="subtract">-</button> {{goods.quantity}} <button @click="add">+</button> </td> <td>{{totalPrice}}</td> </tr> </table> </div> <script> let vm = new Vue({ el: '#app', data: { price: 2999, quantity: 1, goods: { price: 2999, quantity: 1 } }, computed: { totalPrice() { return this.goods.price * this.goods.quantity; }, deepQuantity(){ return this.goods.quantity; } }, methods: { add() { this.goods.quantity++; }, subtract() { this.goods.quantity--; } }, watch: { quantity(newVal, oldVal) { // console.log(newVal, oldVal); this.goods.quantity = newVal <= 0 ? 0 : newVal }, goods: { deep: true, handler:(newVal, oldVal)=> { //console.log(newVal, oldVal); } }, deepQuantity(newVal, oldVal){ console.log("侦听器=",newVal, oldVal); if(newVal<0) alert("购物数量不能为负数!") this.goods.quantity = newVal <= 0 ? 0 : newVal } } }); </script> </body> </html>
效果截图:
Vue操作数组时的注意事项
由于 JavaScript 语言本身的限制,当我们对数组进行某些操作时,Vue 可能不会检测到数组元素的变化,那么进而视图也不会响应。比如:
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> <p v-for="(item,index) in userArr"> {{item.userId}}--{{item.userName}}--{{item.userSex}} </p> <button @click="clear">清空</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { userArr: [{ userId: 1, userName: '张三', userSex: '男' }, { userId: 2, userName: '李四', userSex: '女' }, { userId: 3, userName: '王五', userSex: '男' }] }, methods: { clear() { this.userArr.length = 0; console.log(this.userArr.length); //0 } } }); </script> </body> </html>
效果截图:
上面实例中,将数组的长度设置为0后,数组确实被清空了。但是Vue却不能检测到数组的变化,所以页面视图也不会响应。
为了解决这个问题,Vue给我们提供了如下解决方案: Vue 提供一组观察数组的变异方法(就是这些方法会改变原始数组,所以才会被Vue检测到),使用它们就可以触发视图更新。 这些方法有7个:push()、pop()、shift()、unshift()、splice()、sort()、reverse()。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> <p v-for="(item,index) in userArr"> {{item.userId}}--{{item.userName}}--{{item.userSex}} </p> <button @click="clear">清空</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { userArr: [{ userId: 1, userName: '张三', userSex: '男' }, { userId: 2, userName: '李四', userSex: '女' }, { userId: 3, userName: '王五', userSex: '男' }] }, methods: { clear() { //使用Vue中提供的变异方法 this.userArr.splice(0,this.userArr.length); } } }); </script> </body> </html>
效果动图:
这样就可以检测到数组的变化,同时页面视图会响应。
Vue的表单绑定
v-bind实现了数据的单向绑定,将vue实例中的数据同元素属性值进行绑定,接下来看一下vue中的数据双向绑定v-model。
v-mode实现表单绑定
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> <h3>注册</h3> <form> 用户名:<input type="text" v-model="myForm.username"/><br> 密码:<input type="password" v-model="myForm.password" /><br> 确认密码:<input type="password" v-model="myForm.beginpassword" /><br> 性别:<input type="radio" v-model="myForm.sex" value="0" />男 <input type="radio" v-model="myForm.sex" value="1" />女<br> 爱好:<input type="checkbox" v-model="myForm.like" value="0" />读书 <input type="checkbox" v-model="myForm.like" value="1" />体育 <input type="checkbox" v-model="myForm.like" value="2" />旅游<br> 国籍:<select v-model="myForm.nationality"> <option value="0">中国</option> <option value="1">美国</option> <option value="2">英国</option> </select><br> 个人简介:<textarea v-model="myForm.brief" rows="5" cols="30"></textarea><br> <input type="button" value="提交" @click="handler" /> </form> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { myForm: { username: '', password: '', beginpassword: '', sex: 0, like: [0, 1, 2], nationality: 0, brief: '', level: 0 } }, methods: { handler: function() { console.log(this.myForm.username); console.log(this.myForm.password); console.log(this.myForm.beginpassword); console.log(this.myForm.sex); console.log(this.myForm.like); console.log(this.myForm.nationality); console.log(this.myForm.brief); } } }); </script> </body> </html>
效果截图:
v-model修饰符
v-model中还可以使用一些修饰符来实现某些功能:
v-model.lazy 只有在input输入框发生一个blur时才触发,也就是延迟同步到失去焦点时。
v-model.trim 将用户输入的前后的空格去掉。
v-model.number 将用户输入的字符串转换成number。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> <!-- 输入数据会延迟到失去焦点时才绑定 --> 输入数据1:<input type="text" v-model.lazy="lazyStr" /><br> <!-- 输入数据会自动转换为number,所以可以直接进行运算 --> 输入数据2:<input type="text" v-model.number="numberStr" /><br> <!-- 输入数据会自动去除前后空格 --> 输入数据3:<input type="text" v-model.trim="trimStr" /><br> <button @click="handler" >完成</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { lazyStr: '', numberStr:'', trimStr:'' }, methods: { handler: function() { console.log(this.lazyStr); console.log(this.numberStr); console.log(this.trimStr); } } }); </script> </body> </html>
输入数据123 456,前后都有空格,中间也有空格。
效果动图:
过滤器
过滤器是对即将显示的数据做进一步的筛选处理,然后进行显示,值得注意的是过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据。
过滤器分全局过滤器和本地过滤器(局部过滤器)。
全局过滤器
下面定义一个全局过滤器,用于在数据前加上大写的VUE。需要注意的是,全局过滤器定义必须始终位于Vue实例之上,否则会报错。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> {{ message | toAdd }} </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.filter("toAdd", function(value) { return 'VUE' + value }) var demo = new Vue({ el: '#app', data: { message: '过滤器', } }) </script> </body> </html>
效果截图:
注意:
Vue.filter() 后有两个参数:过滤器名,过滤器处理函数。
过滤器处理函数也有一个参数:要过滤的数据。
本地过滤器
本地过滤器存储在vue组件中,作为filters属性中的函数,我们可以注册多个过滤器存储在其中。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> <p> {{ message | Reverse }} </p> <p> {{ message | Length }} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var demo = new Vue({ el: '#app', data: { message: '过滤器', }, filters: { Reverse: function(value) { if (!value){ return ''; } return value.toString().split('').reverse().join(''); }, Length: function(value) { return value.length; }, }, }) </script> </body> </html>
效果截图:
过滤器传参
过滤器也是可以传递参数的。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> {{ msg1 | toDou (1,2) }} {{ msg2 | toDou (10,20) }} </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.filter('toDou', function(value, param1, param2) { if (param2 < 10) { return value + param1; } else { return value + param2; } }); new Vue({ el: '#app', data: { msg1: 9, msg2: 12, } }); </script> </body> </html>
效果截图:
注意:过滤器处理函数的第一个参数,仍然是要过滤的数据。从第二个参数开始,才是过滤器本身要传递的参数。
串联过滤器
多个过滤器可以串联使用。
样例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="app"> {{ money | prefix | suffix }} </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.filter("prefix", function(value) { return '¥' + value }) Vue.filter("suffix", function(value) { return value + '元' }) var demo = new Vue({ el: '#app', data: { money:10 } }) </script> </body> </html>
效果截图: