1. 程式人生 > 資訊 >除蟎濾塵新形態/手持立式二合一,德爾瑪旗艦店側旋濾吸塵器 129 元

除蟎濾塵新形態/手持立式二合一,德爾瑪旗艦店側旋濾吸塵器 129 元

計算屬性

在模板中使用表示式非常方便,但如果表示式的邏輯過於複雜,模板就會變得臃腫且難以維護,所以引出計算屬性。計算屬性是以函式形式,在computed中定義。比如可以將模板表示式

<p>
    {{ message.split('').reverse().join('')}}
</p>

用計算屬性實現:

<html>
	<head>
		<meta charset="UTF-8">
		<title>計算屬性</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>

計算屬性預設只有getter,因此不能直接修改計算屬性,如果需要,則可以提供一個setter:

<html>
	<head>
		<meta charset="UTF-8">
		<title>計算屬性的getter和setter</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函式實現的。

計算屬性的快取

複雜表示式也可以放到方法中實現,然後在繫結表示式中呼叫方法即可:

<html>
	<head>
		<meta charset="UTF-8">
		<title>計算屬性</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,該方法都會被呼叫。

為了對計算屬性和方法的區別有更直觀的認識,下面同時使用方法和計算屬性:

<html>
	<head>
		<meta charset="UTF-8">
		<title>計算屬性</title>
	</head>
	<body>
		<div id="app">
		  <p>原始字串: {{ message }}</p>
		  <p>計算後的反轉字串: {{ reversedMessage }}</p>
		  <p>方法呼叫後的反轉字串: {{ reversedMessage2() }}</p>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
                data() {
                    return {
                        message: 'Hello,Vue.js無難事!'        
                    }
                },
                computed: {
                    // 計算屬性的 getter
                    reversedMessage: function () {
                        alert("計算屬性");
                        return this.message.split('').reverse().join('');
                    }
                },
                methods: {
                    reversedMessage2: function () {
                    	alert("方法");
                        return this.message.split('').reverse().join('');
                    }
                }
            }).mount('#app');
            let msg = vm.reversedMessage;
            msg = vm.reversedMessage2();
		</script>
	</body>
</html>

在計算屬性的getter函式和reversedMessage2方法中呼叫alert語句顯示一個訊息框,在根元件例項構建後,分別訪問vm.reversedMessage和呼叫vm.reversedMessage2方法。

使用瀏覽器開啟頁面會依次看到計算屬性、方法、方法三個訊息框,前兩個是模板中的花括號被替換顯示的,最後一個方法訊息框是呼叫vm.reversedMessage2顯示的。可以看到最後對vm.reversedMessage計算屬性的訪問並沒有彈出訊息框,這是因為它所依賴的message屬性並未發生改變。

下面程式碼中的計算屬性now在初次渲染後不會再更新,因為Date.now()不是響應式依賴

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

那麼為什麼需要快取呢,假設有一個性能開銷比較大的計算屬性A,然後可能其他的計算屬性依賴於A。如果沒有快取。將不可避免的多次執行A的getter。

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

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<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>

不建議將v-for和v-if一起使用,這是因為即使由於v-if指令的使用只渲染了部分元素,但在每次重新渲染的時候仍然需要遍歷整個列表,而不論渲染的元素內容是否發生了改變。

採用計算屬性過濾後再遍歷,可以獲得以下好處:

  • 過濾後的列表只會在plans陣列發生變化時才被重新計算,過濾更高效。
  • 使用v-for='plan in completedPlans'之後,在渲染的時候只遍歷已完成的工作,渲染更高效。
  • 解耦渲染層的邏輯,可維護性強。

例項:購物車的實現

要想實現購物車首先得有商品資訊:

 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;
         }
     }

接下來使用v-for迴圈輸出商品資訊,用表格進行佈局:

<html>
    <head>
        <meta charset="UTF-8">
        <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>
                {{ book.count }}
                <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>

說明:

  • 在div元素中用v-cloak指令避免頁面載入時的閃爍問題,要和css樣式[v-cloak]{display:none}一起使用。
  • 使用v-for指令時,我們同時使用key屬性。
  • 商品數量的左右兩邊各添加了一個減號和加號按鈕用於遞減和遞增商品數量,當商品數量為0時,通過v-bind:disabled='book.count===0'禁用按鈕。此外,由於這兩個按鈕的功能都很簡單,所以在使用v-on指令時沒有繫結click事件處理方法。
  • 單項商品的價格通過itemPrice方法輸出,總價通過totalPrice輸出,刪除通過v-on繫結deleteItem實現。