zl程序教程

您现在的位置是:首页 >  前端

当前栏目

Vue计算属性

2023-06-13 09:17:20 时间

文章目录

1、计算属性的定义

  表达式的逻辑过于复杂的时候,应当考虑使用计算属性。计算属性是以函数形式,在选项对象的computed选项中定义。我们将字符串翻转的功能用计算属性实现,代码如下:

<!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">
        <p>原始字符串:{{message}}</p>
        <p>计算后的反转字符串:{{reversedMessage}}</p>
    </div>
    
    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm=Vue.createApp({
            data(){
                return{
                    message:'Hello,Java无难事!'
                }
            },
            computed:{
                //计算属性的getter
                reversedMessage(){
                    return this.message.split('').reverse().join('');
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

渲染结果:

当message属性的值改变时,reversedMessage的值也会自动更新,并且会自动同步更新DOM部分。在浏览器的Console窗口中修改vm.message的值,可以发现reversedMessage的值也会随之改变。

  计算属性默认只有getter,因此是泵你直接修改计算属性的,如果需要,则可以提供一个setter,代码如下所示:

<!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">
        <p>First name:<input type="text" v-model="firstName"></p>
        <p>Last name:<input type="text" v-model="lastName"></p>
        <p>{{fullName}}</p>
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm=Vue.createApp({
            data(){
                return{
                    firstName:'Smith',
                    lastName:'Will'
                }
            },
            computed:{
                fullName:{
                    //getter
                    get(){
                        return this.firstName+' '+this.lastName
                    },
                    //setter
                    set(newValue){
                        let names=newValue.split(' ');
                        this.firstName=names[0];
                        this.lastName=names[names.length-1];
                    }
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

渲染结果:

任意修改firstName和lastName的值,fullName的值也会自动更新,这是调用它的getter()函数实现的。在浏览器的Console窗口中输入vm.fullName=“Bruce Willis”,可以看到firstName和lastName的值也同时发生了改变,这是调用fullName的setter函数实现的。

2、计算属性的缓存

  复杂的表达式也可以放到方法中实现,然后在绑定表达式中调用方法即可。 翻转字符串也可以用下面的代码实现:

<!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">
        <p>原始字符串:{{message}}</p>
        <p>计算后的反转字符串:{{reversedMessage()}}</p>
    </div>



    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm=Vue.createApp({
            data(){
                return{
                    message:'Hello,Vue.js无难事!'
                }
            },
            methods:{
                reversedMessage(){
                    return this.message.split('').reverse().join('');
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

既然使用方法能够实现与计算属性相同的结果,那么我们还有必要使用计算属性吗?   答案是有必要,因为计算属性是基于它的响应式依赖进行缓存的,只有在计算属性的相关响应式依赖发生改变时才会更新值。这就意味着只要message还没有发生改变,多次访问reversedMessage计算属性会立即返回之前的计算结果,而不会再次执行函数;而如果采用方法,那么不管什么时候访问reversedMessage(),该方法都会被调用。

3、v-for和v-if一起使用的替代方案

  在渲染列表时,根据v-if指令的条件表达式的计算结果过滤列表中不满足条件的列表项。实际上,使用计算属性完成这个功能会更好一些。   Vue.js的作者不建议将v-for和v-if一起使用,因为即使由于v-if指令的使用只渲染了部分元素,但在每次重新渲染的时候仍然要遍历整个列表,而不论渲染的元素内容是否发生了改变。   采用计算属性过滤后再遍历,可以获得以下好处:

  • 过滤后的列表只会在plans数组发生相关变化时才会被重新计算,过滤更高效。
  • 使用v-for="plan in completedPlans"之后,在渲染的时候只遍历已完成的计划,渲染更高效。
  • 解耦渲染层的逻辑,可维护性更强

代码如下:

<!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>v-for与计算属性</title>
</head>
<body>
    <div id="app">
        <h1>已完成的工作计划</h1>
        <ul>
            <li v-for="plan in completedPlans">
                {{plan.content}}
            </li>
        </ul>
        <h1>未完成的工作计划</h1>
        <ul>
            <li v-for="plan in incompletePlans">
                {{plan.content}}
            </li>
        </ul>
    </div>




    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm=Vue.createApp({
            data(){
                return{
                    plans:[
                        {content:'写《Java无难事》',isComplete:false},
                        {content:'买菜',isComplete:true},
                        {content:'写PPT',isComplete:false},
                        {content:'做饭',isComplete:true},
                        {content:'打羽毛球',isComplete:false}
                    ]
                }
            },
            computed:{
                completedPlans(){
                    return this.plans.filter(plan=>plan.isComplete);
                },
                incompletePlans(){
                    return this.plans.filter(plan=>!plan.isComplete);
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

4、实例:购物车的实现

  要实现一个购物车案例,当然得有商品信息,为了简化,我们直接在代码中给出所有的商品信息。在组件实例的data选项中定义数据。   组件实例的data选项:

data(){
                return{
                    books:[
                        {
                            id:1,
                            title:'Java无难事',
                            price:188,
                            count:1
                        },{
                            id:2,
                            title:'VC++深入详解',
                            price:168,
                            count:1
                        },{
                            id:3,
                            title:'Servlet/JSP深入详解',
                            price:139,
                            count:1
                        }
                    ]
                }
            }

  购物车中的单项商品金额是动态的,是由商品单价和商品的数量相乘得到的。此外,所有商品的总价也是动态的,是所有商品价格相加得到的,所以这两种数据就不适合在book对象的属性中定义了。   采用方法来实现单项商品金额,采用计算属性实现总价,删除操作的事件处理器也定义为一个方法。

 methods:{
                itemPrice(price,count){
                    return price*count;
                },
                deleteItem(index){
                    this.books.splice(index,1);
                }
            }

  说明:单项商品金额的实现方式可以有很多种,本例采用组件实例的方法实现只是为了简单。

使用v-for指令输出商品信息

<div id="app" v-cloak>
        <table>
            <tr>
                <th>序号</th>
                <th>商品名称</th>
                <th>单价</th>
                <th>数量</th>
                <th>金额</th>
                <th>操作</th>
            </tr>
            <tr v-for="(book,index) in books" :key="book.id">
                <td>{{book.id}}</td>
                <td>{{book.title}}</td>
                <td>{{book.price}}</td>
                <td>
                    <button v-bind:disabled="book.count===0" v-on:click="book.count-=1">
                        -
                    </button>
                    <button v-on:click="book.count+=1">+</button>
                </td>
                <td>
                    {{itemPrice(book.price,book.count)}}
                </td>
                <td>
                    <button @click="deleteItem(index)">删除</button>
                </td>
            </tr>
        </table>
        <span>总价¥{{totalPrice}}</span>
    </div>

说明: (1)在<div>元素中,我们使用了v-cloak指令避免页面加载时的闪烁问题,当然,这需要和CSS样式规则[v-cloak]{display:none}一起使用。 (2)使用v-for指令时,我们同时使用了key属性(采用了v-bind的简写语法)。 (3)商品数量的左右两边各添加了一个减号和加号按钮,用于递减和递增商品数量,当商品数量为0时,通过v-bind:disabled="book.count===0"禁用按钮。此外,这两个按钮的功能都很简单,所以在使用v-on指令时,没有绑定click事件处理方法,而是直接使用了JavaScript语句。 (4)单项商品的价格通过调用itemPrice()方法输出。 (5)所有商品总价通过计算属性totalPrice输出。 (6)单项商品的删除通过v-on指令(采用了间歇语法)绑定deleteItem()方法实现。 完整代码如下:

<!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>购物车</title>
    <style>
        body {
            width: 600px;
        }
        table {
            border: 1px solid black;
        }
        table {
            width: 100%;
        }
        th {
            height: 50px;
        }
        th, td {
            border-bottom: 1px solid #ddd;
            text-align: center;
        }
        span {
            float: right;
        }
        
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
    <div id="app" v-cloak>
        <table>
            <tr>
                <th>序号</th>
                <th>商品名称</th>
                <th>单价</th>
                <th>数量</th>
                <th>金额</th>
                <th>操作</th>
            </tr>
            <tr v-for="(book,index) in books" :key="book.id">
                <td>{{book.id}}</td>
                <td>{{book.title}}</td>
                <td>{{book.price}}</td>
                <td>
                    <button v-bind:disabled="book.count===0" v-on:click="book.count-=1">
                        -
                    </button>
                    <button v-on:click="book.count+=1">+</button>
                </td>
                <td>
                    {{itemPrice(book.price,book.count)}}
                </td>
                <td>
                    <button @click="deleteItem(index)">删除</button>
                </td>
            </tr>
        </table>
        <span>总价¥{{totalPrice}}</span>
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm=Vue.createApp({
            data(){
                return{
                    books:[
                        {
                            id:1,
                            title:'Java无难事',
                            price:188,
                            count:1
                        },{
                            id:2,
                            title:'VC++深入详解',
                            price:168,
                            count:1
                        },{
                            id:3,
                            title:'Servlet/JSP深入详解',
                            price:139,
                            count:1
                        }
                    ]
                }
            },
            methods:{
                itemPrice(price,count){
                    return price*count;
                },
                deleteItem(index){
                    this.books.splice(index,1);
                }
            },
            computed:{
                totalPrice(){
                    let total=0;
                    for(let book of this.books){
                        total+=book.price*book.count;
                    }
                    return total;
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

渲染结果: