小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1. 综合案例——书籍购物车
案例说明:
- 在界面上以表格的形式,显示一些书籍的数据;
- 在底部显示书籍的总价格;
- 点击
+
或-
可以增加或减少书籍数量(如果为1
,那么不能继续-
); - 点击移除按钮,可以将书籍移除(当所有书籍都移除完时,显示:“购物车为空~”);
初步实现如下:
- 目录结构:
-
index.html
中的内容:<!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> <link rel="stylesheet" href="style.css"> </head> <body> <div id="app"></div> <template id="my-app"> <template v-if="books.length > 0"> <table> <thead> <th>序号</th> <th>书籍名称</th> <th>出版日期</th> <th>价格</th> <th>购买数量</th> <th>操作</th> </thead> <tbody> <tr v-for="(book, index) in books"> <td>{{ index + 1 }}</td> <td>{{ book.name }}</td> <td>{{ book.date }}</td> <td>¥{{ book.price }}</td> <td> <button :disabled="book.count <= 1" @click='decrement(index)'>-</button> <span class="counter">{{ book.count }}</span> <button @click="increment(index)">+</button> </td> <td> <button @click="removeBook(index)">移除</button> </td> </tr> </tbody> </table> <h2>总价:¥{{totalPrice}}</h2> </template> <template v-else> <h2>购物车为空~</h2> </template> </template> <script src="../js/vue.js"></script> <script src="./index.js"></script> </body> </html>
-
index.js
中的内容:Vue.createApp({ data() { return { books: [ { id: 1, name: '《算法导论》', date: '2006-9', price: 85.00, count: 1 }, { id: 2, name: '《UNIX编程艺术》', date: '2006-2', price: 59.00, count: 1 }, { id: 3, name: '《编程珠玑》', date: '2008-10', price: 39.00, count: 1 }, { id: 4, name: '《代码大全》', date: '2006-3', price: 128.00, count: 1 }, ] } }, computed: { totalPrice() { let total = 0; for (const book of this.books) { total += book.price * book.count; } return total; // 或者使用数组的归并方法 reduce() // return this.books.reduce((prev, curr) => prev + curr.price * curr.count, 0); } }, methods: { decrement(index) { // 通过索引值拿到对象 this.books[index].count--; }, increment(index) { this.books[index].count++; }, removeBook(index) { this.books.splice(index, 1); } }, template: '#my-app' }).mount('#app');
-
style.css
中的内容:table { /* border: 1px solid #999; */ border-collapse: collapse; /* border-spacing: 0; */ } th, td { border: 1px solid #e9e9e9; padding: 8px 16px; text-align: left; } th { background-color: #f7f7f7; color: #5c6b77; font-weight: 600; } .counter { margin: 0 5px; }
页面效果如下:
现在,我们来思考一个问题:价格前面的人民币符号(¥
)直接写在模板中的做法合适吗?
不合适。因为首先,如果模板中有很多地方要显示价格,那么价格前面都要加上这个人民币符号;而且如果有多个界面都要显示价格,那么每个界面上在显示价格时,前面也都要加上人民币符号。这样看来这种直接修改模板的做法就显得有点笨拙了。那有没有更好的办法呢?有,在 Vue2
中,我们可以使用过滤器来给价格添加上人民币符号。但是,从 Vue 3.0
开始,过滤器已移除,且不再支持。取而代之的是,建议用计算属性或方法调用来实现。
下面我们就分别用计算属性和方法来实现 Vue2
中过滤器的效果。
1.1 Vue3
中使用计算属性替代过滤器
我们对前面 index.js
文件中的 computed
选项中的内容进行修改:
computed: {
totalPrice() {
let total = 0;
for (const book of this.books) {
total += book.price * book.count;
}
// return total;
return '¥' + total;
},
filterBooks() {
const filteredBooks = this.books.map(book => {
// 注意这里是对 books 中的对象做了修改,所以上面计算属性 totalPrice 中的 book.price 取到的会是这里修改后的值(价格前加了¥)
// 原因是这里 filteredBooks 数组中的对象元素和 books 数组中的对象元素其实是指向的同一个对象的引用
//(数组中的对象元素其实保存的是对象的引用)
// book.price = '¥' + book.price;
// return book;
// 为了不影响其它地方使用 book.price,我们这里拷贝一份 book 后再使用
const tempBook = Object.assign({}, book);
tempBook.price = '¥' + book.price;
return tempBook;
});
return filteredBooks;
}
}
再将 index.html
文件中对 books
的遍历改为对 filterBooks
的遍历:
<tr v-for="(book, index) in filterBooks">
同时去掉模板中价格前面的人民币符号(¥
):
<td>{{ book.price }}</td>
<h2>总价:{{totalPrice}}</h2>
最终页面的效果和之前是一样的。
1.2 Vue3
中使用方法调用替代过滤器
我们也可以通过调用方法来实现同样的效果,在 index.js
文件中的 methods
选项中添加方法:
methods: {
// ...
formatPrice(price) {
return '¥' + price;
}
}
将 computed
选项中的计算属性 totalPrice
的返回值前的人民币符号取消掉:
totalPrice() {
let total = 0;
for (const book of this.books) {
total += book.price * book.count;
}
return total;
}
再将 index.html
文件中书籍的遍历设置为对 books
的遍历:
<tr v-for="(book, index) in books">
同时在模板中需要展示价格的地方调用我们添加的方法:
<td>{{ formatPrice(book.price) }}</td>
<h2>总价:{{formatPrice(totalPrice)}}</h2>
最终页面的效果和之前是一样的。
当然,本案例中我们是把给价格添加人民币符号的方法定义在当前的 Vue
实例中的,而如果有另外一个页面中也要用到这个方法该怎么办呢?这时为了方便使用,我们就可以通过形如 app.config.globalProperties.$filters
的全局属性把方法定义到全局了,相当于实现了全局过滤器,这样就可以在任何地方直接拿到这个方法了。具体我们后面再讲。