表达式的逻辑过于复杂时,应当考虑使用计算属性。计算属性主要用于封装对现有对象的各种操作,并返回一个新的数据。可以像普通的数据属性一样使用,不过要注意,如果要修改计算属性,需要为它提供setter方法。此外,计算属性会根据依赖的数据变化而更新,并且在该数据没有变化时,使用缓存的计算属性数据。

1、定义计算属性

计算属性是以函数形式,在vue实例的选项对象的computed选项中定义。

	<div id = "app">
        <p>原始字符串:{{message}}</p>
        <p>计算后的反转字符串:{{reversedMessage}}</p>
    </div>
    <script>
        var vm=new Vue({
            el:'#app',
            data:{
                message:'hello world! hey!'
            },
            computed:{
                //计算属性的 getter
                reversedMessage:function(){
                    return this.message.split('').reverse().join('')
                }
            }
        })
    </script>

上例中声明了一个计算属性reversedMessage,给出的函数将用作属性vm.reversedMessage的getter函数。

计算属性默认只有getter,因此是不能直接修改计算属性的,如有需要也可以提供一个setter。

	<div id = "app">
        <p>原始字符串:{{message}}</p>
        <p>计算后的反转字符串:{{reversedMessage}}</p>
        <p>firstName:<input type="text" v-model="firstName"></p>
        <p>lastName:<input type="text" v-model="lastName"></p>
        <p>{{fullName}}</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        var vm=new Vue({
            el:'#app',
            data:{
                message:'hello world! hey!',
                firstName:'Smith',
                lastName:'Will'
            },
            computed:{            
                fullName:{
                    //getter
                    get: function(){
                        return this.firstName+' '+this.lastName
                    },
                    //setter
                    set:function(newValue){
                        var names=newValue.split(' ')
                        this.firstName=names[0]
                        this.lastName=names[names.length-1]

                    }
                },
                //计算属性的 getter
                reversedMessage:function(){
                    return this.message.split('').reverse().join('')
                }
            }
        })
    </script>

上例中,可以在控制台输入vm.fullName="Bruce wills"修改计算属性fullName的值。

2、计算属性缓存

复杂的表达式也可以放到方法中去实现(methods),然后调用方法即可。

注意:调用计算属性直接用函数名,调用方法要在函数名后加括号()。

既然使用方法能实现与计算属性相同的结果,那么还有必要使用计算属性吗?

答案是有必要的。
因为计算属性是基于它的响应式依赖进行缓存的,只有在计算属性的相关响应式依赖发生改变时才会重新求值。
这意味着,只要message没有发生改变,多次访问reversedMessage计算属性会立即返回之前的计算结果,而不会再次执行函数。
而如果采用方法(methods),不管什么时候访问,都会重新执行函数。

下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

	computed: {  
		now: function () {    
			return Date.now()  
		}
	}

为什么需要缓存?

假设有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后可能有其他的计算属性依赖于 A 。如果没有缓存,将不可避免的多次执行 A 的 getter。

如果不希望有缓存,请用方法来替代。

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

v-for和v-if一起使用,可以在渲染列表时,根据v-if指令的条件判断来过滤列表中不满足条件的列表项。
这个功能可以用计算属性来完成。

	<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="vue.js"></script>
	<script>
		var vm = new Vue({
		  el: '#app',
		  data: {
				plans: [
					{content: '写《Vue.js无难事》', isComplete: false},
					{content: '买菜', isComplete: true},
					{content: '写PPT', isComplete: false},
					{content: '做饭', isComplete: true},
					{content: '打羽毛球', isComplete: false}
				]
			},
		  computed: {
		  	// 计算属性的 getter
		    completedPlans: function () {
		      return this.plans.filter(plan => plan.isComplete);
		    },
		    incompletePlans: function(){
		    	return this.plans.filter(plan => !plan.isComplete);
		    }
		  }
		})
	</script>

不建议v-for和v-if同时用在一个元素上。因为即使由于v-if指令的使用而渲染了部分元素,但每次重新渲染的时候仍要遍历整个列表,而不论渲染的元素是否发生了改变。

采用计算属性过滤后再遍历,可以获得如下好处:

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

4、实例:购物车列表的实现

购物车列表效果图

<!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>
    <style>
        body{
            width:600px;
        }
        table{
            border: 1px solid black;
            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>
            <thead>
                <tr>
                    <th>编号</th>
                    <th>商品名称</th>
                    <th>单价</th>
                    <th>数量</th>
                    <th>金额</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="(book,index) in books" :key="book.id">
                    <td>{{book.id}}</td>
                    <td>{{book.title}}</td>
                    <td>{{book.price}}</td>
                    <td>
                        <button :disabled="book.count==1" @click="book.count--">-</button>
                        {{book.count}}
                        <button @click="book.count++">+</button>
                    </td>
                    <td>{{itemPrice(book.price, book.count)}}</td>
                    <td><button @click="deleteItem(index)">删除</button></td>
                </tr>
            </tbody>
        </table>
        <span>总价:¥{{totalPrice}}</span>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        var vm=new Vue({
            el:'#app',
            data:{
                books:[
                    {
                        id:1,
                        title:'vue.js无难事',
                        price:98,
                        count:1
                    },
                    {
                        id:2,
                        title:'vue.js无难事',
                        price:98,
                        count:1
                    },
                    {
                        id:3,
                        title:'vue.js无难事',
                        price:98,
                        count:1
                    },
                    {
                        id:4,
                        title:'vue.js无难事',
                        price:98,
                        count:1
                    },
                    {
                        id:5,
                        title:'vue.js无难事',
                        price:98,
                        count:1
                    }
                ]
            },
            methods:{
                itemPrice(price, count){
                    return price*count;
                },
                deleteItem(index){
                    this.books.splice(index,1);
                }
            },
            computed:{
                totalPrice(){
                    var total = 0;
                    for(let book of this.books){
                        total+=book.price*book.count;
                    }
                    return total;
                }
            }

        })
    </script>
</body>
</html>
Logo

前往低代码交流专区

更多推荐