概述
通常我們會(huì)在模版中綁定表達(dá)式,但如果表達(dá)式的邏輯過(guò)于復(fù)雜指攒,則模版會(huì)變得臃且難以維護(hù)。
例:Mustache語(yǔ)法中的表達(dá)式調(diào)用了三個(gè)方法來(lái)最終實(shí)現(xiàn)字符串的反轉(zhuǎn)
<div id="app">
<p>{{message.split('').reverse().join('')}}</p>
</div>
定義計(jì)算屬性
表達(dá)式的邏輯過(guò)于復(fù)雜的時(shí)候盛垦,都應(yīng)當(dāng)考慮使用計(jì)算屬性纪蜒。計(jì)算屬性是以函數(shù)形式蹭秋,在Vue實(shí)例的選項(xiàng)對(duì)象的computed選項(xiàng)中定義。我們將上面字符串反轉(zhuǎn)的功能使用計(jì)算屬性來(lái)實(shí)現(xiàn)堤撵。
代碼示例如下:
<div="app">
<p>原始字符串:{{message}}</p>
<p>計(jì)算后的反轉(zhuǎn)字符串:{{reversedMessage}}</p>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Hello!'
},
computed: {
// 計(jì)算屬性的getter
reversedMessage: function() {
return this.message.split('').reverse().join('')
}
}
});
</script>
我們聲明了一個(gè)計(jì)算屬性reversedMessage仁讨,給出的函數(shù)將用作屬性vm.reversedMessage的getter函數(shù)。
當(dāng)message屬性的值改變時(shí)实昨,reversedMessage的值也會(huì)自動(dòng)更新洞豁,并且會(huì)自動(dòng)同步更新DOM部分。當(dāng)瀏覽器的控制臺(tái)窗口中修改vm.message的值荒给,可以發(fā)現(xiàn)reversedMessage的值也會(huì)隨之改變丈挟。
計(jì)算屬性默認(rèn)只有g(shù)etter,因此是不能直接修改計(jì)算屬性的志电,如果需要也可以提供一個(gè)setter曙咽。
代碼示例如下:
<div="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>
var vm = new Vue({
el: '#app',
data: {
firstName: 'John',
lastName: 'Tierney'
},
computed: {
fullName: {
// getter
get: function() {
return this.firstName + ' ' + this.lastName
},
// setter
set: function(newName) {
var names = newName.split('')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
},
});
</script>
- 任意修改firstName或者lastName的值,fullName的值也會(huì)自動(dòng)更新挑辆,這是調(diào)用它的getter函數(shù)來(lái)實(shí)現(xiàn)的例朱。在瀏覽器的控制臺(tái)窗口中輸入vm.fullName="Bruce Willis",可以看到firstName和lastName的值也同時(shí)發(fā)生了改變鱼蝉,這時(shí)調(diào)用fullName的setter函數(shù)來(lái)實(shí)現(xiàn)的洒嗤。
計(jì)算屬性緩存
復(fù)雜的表達(dá)式也可以放到方法中去實(shí)現(xiàn),然后在綁定表達(dá)式中調(diào)用方法即可魁亦。
例:使用方法實(shí)現(xiàn)字符串翻轉(zhuǎn)
<div="app">
<p>原始字符串:{{message}}</p>
<p>計(jì)算后的反轉(zhuǎn)字符串:{{reversedMessage}}</p>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Hello!'
},
methods: {
// 計(jì)算屬性的getter
reversedMessage: function() {
return this.message.split('').reverse().join('')
}
}
});
</script>
既然使用方法能實(shí)現(xiàn)與計(jì)算屬性相同的結(jié)果渔隶,那么為什么還要使用計(jì)算屬性呢?
因?yàn)橛?jì)算屬性是基于它的響應(yīng)式依賴(lài)進(jìn)行緩存的洁奈,只有在計(jì)算屬性的相關(guān)響應(yīng)式依賴(lài)發(fā)生改變時(shí)才會(huì)重新求值间唉。這就意味著只要message還沒(méi)有發(fā)生改變,多次訪問(wèn)reversedMessage計(jì)算屬性會(huì)立即返回之前的計(jì)算結(jié)果睬魂,而不會(huì)再次執(zhí)行函數(shù)终吼;而如果采用方法镀赌,那么不管什么時(shí)候訪問(wèn)reversedMessage()方法氯哮,該方法都會(huì)被調(diào)用。
例:同時(shí)使用方法和計(jì)算屬性
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>計(jì)算屬性</title>
</head>
<body>
<div id="app">
<p>原始字符串:{{message}}</p>
<p>計(jì)算后的反轉(zhuǎn)字符串:{{reversedMessage}}</p>
<p>方法調(diào)用后的反轉(zhuǎn)字符串:{{reversedMessage2()}}</p>
</div>
<script src="./vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Hello!'
},
computed: {
// 計(jì)算屬性的getter
reversedMessage: function() {
alert("計(jì)算屬性")
return this.message.split('').reverse().join('')
}
},
methods: {
reversedMessage2: function() {
alert("方法")
return this.message.split('').reverse().join('')
}
}
})
let msg = vm.reversedMessage
mst = vm.reversedMessage2()
</script>
</body>
</html>
我們?cè)谟?jì)算屬性reversedMessage的getter函數(shù)和方法reversedMessage2()中調(diào)用alert()語(yǔ)句顯示一個(gè)消息框商佛,在Vue實(shí)例構(gòu)建后喉钢,我們分別訪問(wèn)vm.reversedMessage計(jì)算屬性和調(diào)用vm.reversedMessage2()方法。
使用瀏覽器打開(kāi)該頁(yè)面良姆,可以依次看到“計(jì)算屬性”“方法”“方法”共三個(gè)消息框肠虽,前兩個(gè)消息框是模版中的Mustache標(biāo)簽被替換時(shí)顯示的,最后一個(gè)“方法”消息框是代碼最后調(diào)用vm.reversedMessage2()方法顯示的玛追,可以看到最后對(duì)vm.reversedMessage計(jì)算屬性的訪問(wèn)并沒(méi)有彈出消息框税课,這是因?yàn)樗蕾?lài)的message屬性并未發(fā)生改變闲延。
下面代碼中的計(jì)算屬性now在初次渲染后不會(huì)再更新,因?yàn)镈ate.now()不是響應(yīng)式依賴(lài)韩玩。
computed: {
now: function() {
return Date.now()
}
}
v-for和v-if一起使用的替代方案
在前文的指令篇中垒玲,講到了將v-for和v-if一起使用,在渲染列表時(shí)找颓,根據(jù)v-if指令的條件判斷來(lái)過(guò)濾列表中不滿(mǎn)足條件的列表項(xiàng)合愈。實(shí)際上,這個(gè)功能也可以使用計(jì)算屬性來(lái)完成击狮。
修改前文代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-for與計(jì)算屬性</title>
</head>
<body>
<div id="app">
<h1>已完成的工作計(jì)劃</h1>
<ul>
<li v-for="plan in completedPlans">
{{plan.content}}
</li>
</ul>
<h1>未完成的工作計(jì)劃</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: 'planA', isComplete: false},
{content: 'planB', isComplete: true},
{content: 'planC', isComplete: false},
{content: 'planD', isComplete: true},
{content: 'planE', isComplete: false}
]
},
computed: {
// 計(jì)算屬性的getter
completedPlans: function() {
return this.plans.filter(plan => plan.isComplete)
},
incompletePlans: function() {
return this.plans.filter(plan => !plan.isComplete)
}
}
})
</script>
</body>
</html>
這里不建議把v-for和v-if同時(shí)用在同一個(gè)元素上佛析,這是因?yàn)殡m然v-if指令只渲染了部分元素,但在每次重新渲染時(shí)仍然要遍歷整個(gè)列表彪蓬,而不論渲染的元素是否發(fā)生過(guò)改變寸莫。
采用計(jì)算屬性過(guò)濾后再遍歷,可以獲得如下好處:
- 過(guò)濾后的列表只會(huì)在plans數(shù)組發(fā)生相關(guān)變化時(shí)才被重新計(jì)算寞焙,過(guò)濾更高效储狭。
- 使用v-for="plan in completedPlans"之后,在渲染的時(shí)候只遍歷已完成的計(jì)劃捣郊,渲染更高效辽狈。
- 解耦渲染層的邏輯,可維護(hù)性(對(duì)邏輯的更改和擴(kuò)展)更強(qiáng)呛牲。