Vue计算属性
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>
渲染结果:
- 点赞
- 收藏
- 关注作者
评论(0)