1.Vue方法、计算属性及监听器

在vue中处理复杂的逻辑的时候,我们经常使用计算属性、方法及监听器。

  1. methods:方法:它们是挂载在Vue对象上的函数,通常用于做事件处理函数,或自己封装的自定义函数。
  2. computed:计算属性:在Vue中,我们可以定义一个计算属性,这个计算属性的值,可以依赖于某个data中的数据。或者说:计算属性是对数据的再加工处理。
  3. watch:监听器:如果我们想要在数据发生改变时做一些业务处理,或者响应某个特定的变化,我们就可以通过监听器,监听数据的变化,从而做出相应的反应。

1.1.computed 计算属性

计算属性是根据依赖关系进行缓存的计算,并且只在需要的时候进行更新。

  1. <body>
  2. <div id="app">
  3. <p>原数据:{{msg}}</p>
  4. <p>新数据:{{reversedMsg}}</p>
  5. </div>
  6. <script src="https://unpkg.com/vue@next"></script>
  7. <script>
  8. Vue.createApp({
  9. data(){
  10. return {
  11. msg: 'hello world!'
  12. }
  13. },
  14. computed: {
  15. reversedMsg() {
  16. return this.msg.split('').reverse().join('');
  17. }
  18. }
  19. }).mount('#app')
  20. </script>
  21. </body>

一个案例:根据商品数量修改总价

<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://unpkg.com/vue@next"></script>
    <script>
        Vue.createApp({
            data() {
                return {
                    price:2999,
                    quantity:1
                }
            },
            computed:{
                totalPrice(){
                    return this.price*this.quantity;
                }
            },
            methods:{
                add(){
                    this.quantity++;
                },
                subtract(){
                    this.quantity--;
                }
            }
        }).mount('#app')
    </script>
</body>

另一个案例:对数据进行过滤处理。

<body>
    <div id="app">
        {{moneyStr}}
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
        Vue.createApp({
            data() {
                return {
                    money: 100
                }
            },
            computed:{
                moneyStr(){
                    return this.money + '¥'
                }
            }
        }).mount('#app')
    </script>
</body>

1.2.methods 方法

在使用vue的时候,可能会用到很多的方法,它们可以将功能连接到事件的指令,甚至只是创建一个小的逻辑就像其他函数一样被重用。接下来我们用方法实现上面的字符串反转。

<body>
    <div id="app">
        <p>原数据:{{msg}}</p>
        <p>新数据:{{reversedMsg()}}</p>
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
        Vue.createApp({
            data(){
                return {
                    msg: 'hello world!'
                }
            },
            methods: {
                reversedMsg() {
                    return this.msg.split('').reverse().join('');
                }
            }
        }).mount('#app');
    </script>
</body>

虽然使用computed和methods方法来实现反转,两种方法得到的结果是相同的,但本质是不一样的。

计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变的时候才会重新求值,这就意味着只要message还没有发生改变,多次访问reversedMessage计算属性立即返回的是之前计算的结果,而不会再次执行计算函数。

而对于methods方法,只要发生重新渲染,methods调用总会执行该函数。

如果某个computed需要的遍历一个极大的数组和做大量的计算,可以减小性能开销,如果不希望有缓存,则用methods。

1.3.watch 监听器

watch能够监听数据的改变。监听之后会调用一个回调函数。
此回调函数的参数有两个:

  1. 更新后的值(新值)
  2. 更新前的值(旧值)

1.3.1.监听基本数据类型

下面使用watch来监听商品数量的变化。如果商品数量小于1,就重置成上一个值。

<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://unpkg.com/vue@next"></script>
    <script>
        Vue.createApp({
            data() {
                return {
                    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
                }
            }
        }).mount('#app')
    </script>
</body>

1.3.2.深度监听

在上面的例子中,监听的简单的数据类型,数据改变很容易观察,但是当需要监听的数据变为对象类型的时候,上面的监听方法就失效了,因为上面的简单数据类型属于浅度监听,对应的对象类型就需要用到深度监听,只需要在上面的基础上加上deep: true就可以了。

<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 src="https://unpkg.com/vue@next"></script>
    <script>
        Vue.createApp({
            data() {
                return {
                    goods:{
                        price: 2999,
                        quantity: 1
                    }
                }
            },
            computed: {
                totalPrice() {
                    return this.goods.price * this.goods.quantity;
                }
            },
            methods: {
                add() {
                    this.goods.quantity++;
                },
                subtract() {
                    this.goods.quantity--;
                }
            },
            watch:{
                goods:{
                    handler(newVal,oldVal){
                        /**
                         * 注意:虽然使用深度监听,可以监听到对象的改变。
                         *      但是,由于是对象类型,所以newVal与oldVal都指向同一个对象。
                         *      所以,newVal与oldVal中的quantity都是改变后的新值。
                         */
                        console.log(newVal,oldVal);
                        this.goods.quantity = newVal.quantity<=0?oldVal.quantity:newVal.quantity;
                    },
                    deep:true
                }
            }
        }).mount('#app')
    </script>
</body>
  • 上面代码中,由于监听的是对象类型,所以newVal与oldVal都指向同一个对象。
  • 所以,在深度监听对象时,是不能正确获取更新前的对象和更新后的对象的。

解决方案:利用计算属性将对象变成字符串后再监听。

<!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">
        商品名称:{{goods.name}}; 数量:
        <button @click="sub">-</button>{{goods.quantity}}<button @click="add">+</button>
        <br>
        总价:{{total}}
    </div>

    <script src="../js/vue3.js"></script>
    <script>
        Vue.createApp({
            data() {
                return {
                    goods:{
                        name: '小米手机',
                        quantity: 1
                    }
                }
            },
            computed:{
                total(){
                    return this.goods.quantity*100;
                },
                goodsStr(){       //这里新添加一个计算属性,将对象转变为字符串
                    return JSON.stringify(this.goods);
                }
            },
            methods:{
                add(){
                    this.goods.quantity++;
                },
                sub(){
                    this.goods.quantity--;
                }
            },
            watch:{
                goodsStr(newVal,oldVal){    //这里不再监听对象,而是监听字符串
                    let newGoods = JSON.parse(newVal);
                    let oldGoods = JSON.parse(oldVal);
                    this.goods.quantity = newGoods.quantity<=0?oldGoods.quantity:newGoods.quantity;
                }
            }
        }).mount('#app');
    </script>
</body>

</html>

2.Vue的表单绑定

v-bind实现了数据的单向绑定,将vue实例中的数据同元素属性值进行绑定,接下来看一下vue中的数据双向绑定v-model。

2.1.v-mode实现表单绑定

<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://unpkg.com/vue@next"></script>
    <script>
        Vue.createApp({
            data() {
                return {
                    myForm: {
                        username: '',
                        password: '',
                        beginpassword: '',
                        sex: 0,
                        like: ['0', '1', '2'],
                        nationality: 0,
                        brief: ''
                    }
                }
            },
            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);
                }
            }
        }).mount('#app')
    </script>
</body>

2.2.v-model修饰符

v-model中还可以使用一些修饰符来实现某些功能:

  1. v-model.lazy 只有在input输入框发生一个blur时才触发,也就是延迟同步到失去焦点时。
  2. v-model.trim 将用户输入的前后的空格去掉。
  3. v-model.number 将用户输入的字符串转换成number。
<body>
    <div id="app">
        <!-- 输入数据会延迟到失去焦点时才绑定 -->
        {{lazyStr}}<input type="text" v-model.lazy="lazyStr" /><br>
        <!-- 输入数据会自动转换为number,所以可以直接进行运算 -->
        {{numberStr+1}}<input type="text" v-model.number="numberStr" /><br>
        <!-- 输入数据会自动去除前后空格 -->
        {{trimStr}}<input type="text" v-model.trim="trimStr" /><br>
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
        Vue.createApp({
            data() {
                return {
                    lazyStr: '',
                    numberStr: '',
                    trimStr: ''
                }
            }
        }).mount('#app')
    </script>
</body>