购物车案例
Cart.vue
<template>
<div>
<h2>{{title}}</h2>
<table border="1">
<tr>
<th>#</th>
<th>课程</th>
<th>单价</th>
<th>数量</th>
<th>总价</th>
</tr>
<tr v-for="(item,index) in cart" :key="item.id">
<td>
<!-- 通过v-model绑定active,判断是否选中 -->
<input type="checkbox" v-model="item.active" />
</td>
<td>{{item.title}}</td>
<td>{{item.price}}</td>
<td>
<!-- 给button绑定的函数传入index,用于区分是对哪一行数据进行修改 -->
<button @click="handleSub(index)">-</button>
{{item.count}}
<button @click="handleAdd(index)">+</button>
</td>
<td>¥{{item.count*item.price}}</td>
</tr>
<tr>
<td></td>
<!-- activeCount是选中的项,count是购物车中的项,通过computed计算属性能够产生缓存 -->
<td colspan="2">{{activeCount}}/{{count}}</td>
<!-- 总价 -->
<td colspan="2">{{total}}</td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: "cart",
//通过props接收父组件中传入的数据
props: ["title", "cart"],
methods: {
remove(i) {
if (window.confirm("是否确定删除")) {
this.cart.splice(i, 1);
}
},
handleSub(i) {
let count = this.cart[i].count;
count > 1 ? this.cart[i].count-- : this.remove(i);
},
handleAdd(i) {
this.cart[i].count++;
},
},
computed: {
count() {
//返回购物车中的项
return this.cart.length;
},
activeCount() {
//通过过滤器,将购物车中选中的项筛选出来
return this.cart.filter((v) => v.active).length;
},
total() {
//通过forEach遍历
/* let sum = 0;
this.cart.forEach((c) => {
if (c.active) {
sum += c.count * c.price;
}
});
return sum; */
//通过reduce遍历,传入的初始值0会直接给sum,后面的c就是当前调用reduce的数组中正在处理的值
return this.cart.reduce((sum, c) => {
if (c.active) {
sum += c.count * c.price;
}
return sum;
}, 0);//传入一个初始值
},
},
};
</script>
<style scoped>
</style>
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<h1>{{title}}</h1>
<ul>
<li v-for="item in cartList" :key="item.id">
<h2>{{item.title}}</h2>
<p>¥{{item.price}}</p>
</li>
</ul>
<!-- 绑定传入给子组件的数据 -->
<my-cart :title="title" :cart="cartList"></my-cart>
</div>
</template>
<script>
//导入Cart组件,一定要注意,这边导入之后还要在components进行挂载
import MyCart from "./components/Cart";
export default {
name: "App",
data() {
return {
cartList: [
{ id: 1, title: "Vue实战开发", price: 188, active: true, count: 1 },
{ id: 2, title: "React实战开发", price: 288, active: true, count: 1 },
],
title: "购物车",
};
},
//挂载
components: {
MyCart,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
用到的一些方法
filter、forEach和reduce
1.filter
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
语法
arr.filter(callback(element,[index],[array]))
callback 被调用时传入三个参数:
- 数组中当前正在处理的元素。
- 正在处理的元素在数组中的索引。(可选)
- 调用了 filter 的数组本身。(可选)
返回值
一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。
2.forEach
forEach() 方法对数组的每个元素执行一次给定的函数。
语法
arr.forEach(callback(currentValue,[index],[array]))
可依次向 callback 函数传入三个参数:
- 数组中正在处理的当前元素。
- 数组中正在处理的当前元素的索引。(可选)
- forEach() 方法正在操作的数组。(可选)
返回值
undefined。
3.reduce
reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
语法
arr.reduce(callback(acc,cur,[idx],[src]),[initialValue])
callback 函数接收4个参数:
- 累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue
- 数组中正在处理的元素。
- 数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则从索引1起始。(可选)
- 调用reduce()的数组(可选)
initialValue 作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。(可选)
返回值
函数累计处理的结果
使用MockServe保存数据
上面的示列中我们将数据直接保存在data()中,这是很不科学的,下面将通过MockServe模拟从后台拿数据。
- 在当前项目目录下创建
vue.config.js文件
**注意:**每次对这个文件进行了修改之后,一定要重启一下项目才能生效。
可通过在终端输入
ctrl+c : 终止批处理操作吗(Y/N)
退出,然后再重新输入npm run serve,重新运行项目
//commonjs规范
module.exports = {
devServer: {
//每次修改完这个文件,一定要重新启动才能生效
//mock模拟数据
//app相当于后端提供的一个变量对象,也可以是其他命名,
before(app, server) {
//第一个参数 填写自己想设计的接口
//补全地址 http://localhost:8080/api/cartList
//第二个参数 是一个回调函数
app.get('/api/cartList', (req, res) => {
//返回一个json数据格式
res.json({
result: [{
id: 1,
title: "Vue实战开发",
price: 188,
active: true,
count: 1
},
{
id: 2,
title: "React实战开发",
price: 288,
active: true,
count: 1
}
]
})
})
}
}
}
- 在终端当前项目目录下下载axios
在终端输入,下面这行指令即可
npm i axios -s
在main.js中导入axios,并将axios挂载到Vue的原型上
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'//导入axios
Vue.config.productionTip = false
Vue.prototype.$http = axios;//将axios挂载到vue原型上,以后在vue实例中可以直接通过this.$http调用
new Vue({
render: h => h(App),
}).$mount('#app')
此时可以在App.vue的create()中请求数据
async created() {
/* this.$http
.get("/api/cartList")
.then((res) => {
this.cartList = res.data.result;
})
.catch((err) => {
console.log(err);
}); */
try {
//发起请求获取数据
const res = await this.$http.get("/api/cartList");
//将获取到的数据赋值
this.cartList = res.data.result;
} catch (error) {
console.log(error);
}
},
数据持久化
首先在main.js中创建一个中央事件总线$bus,并绑定到Vue的原型上
Vue.prototype.$bus = new Vue();
然后我们模拟将商品添加到购物车,给每一个商品li中添加一个按钮<button @click="addCart(index)">添加购物车</button>并给它绑定点击事件。在addCart()方法中通过this.$bus.$emit("addCart", good);触发在Cart.vue中中央事件总线绑定的事件。
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<h1>{{title}}</h1>
<ul>
<li v-for="(item,index) in cartList" :key="item.id">
<h2>{{item.title}}</h2>
<p>¥{{item.price}}</p>
<button @click="addCart(index)">添加购物车</button>
</li>
</ul>
<!-- -->
<my-cart :title="title"></my-cart>
</div>
</template>
<script>
//
import MyCart from "./components/Cart";
export default {
name: "App",
data() {
return {
cartList: [],
title: "购物车",
};
},
methods: {
addCart(i) {
const good = this.cartList[i];
this.$bus.$emit("addCart", good);//good将作为addCard函数的参数传入
},
},
async created() {
try {
const res = await this.$http.get("/api/cartList");
this.cartList = res.data.result;
} catch (error) {
console.log(error);
}
},
//
components: {
MyCart,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
在Cart.vue中的created方法通过中央事件总线this.$bus绑定被触发的函数,首先通过find()查找是否该商品已经添加到购物车,如果已经添加,则count+1,如果没有添加,则添加到购物车。
模拟添加商品到购物车已经完成,下面进行数据持久化,保存到本地localStorage
通过watch对cart进行深度监听,一旦cart中的值发生改变则通过localStorage将cart保存到本地。同时cart: JSON.parse(localStorage.getItem("cart")) || [],保证每次刷新后都能立马得到值。
**注意:**localStorage只能存储字符串,首先通过JSON.stringify()将对象转成字符串后再存储,取的时候通过JSON.parse()将字符串转成对象。
Cart.vue
<template>
<div>
<h2>{{title}}</h2>
<table border="1">
<tr>
<th>#</th>
<th>课程</th>
<th>单价</th>
<th>数量</th>
<th>总价</th>
</tr>
<tr v-for="(item,index) in cart" :key="item.id">
<td>
<!-- 通过v-model绑定active,判断是否选中 -->
<input type="checkbox" v-model="item.active" />
</td>
<td>{{item.title}}</td>
<td>{{item.price}}</td>
<td>
<!-- 给button绑定的函数传入index,用于区分是对哪一行数据进行修改 -->
<button @click="handleSub(index)">-</button>
{{item.count}}
<button @click="handleAdd(index)">+</button>
</td>
<td>¥{{item.count*item.price}}</td>
</tr>
<tr>
<td></td>
<!-- activeCount是选中的项,count是购物车中的项,通过computed计算属性能够产生缓存 -->
<td colspan="2">{{activeCount}}/{{count}}</td>
<!-- 总价 -->
<td colspan="2">{{total}}</td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: "cart",
//通过props接收父组件中传入的数据
props: ["title"],
data() {
return {
cart: JSON.parse(localStorage.getItem("cart")) || [],
};
},
watch: {
//对cart进行深度监听
cart: {
deep: true,
handler(newV, oldV) {
this.setLocalData(newV);
},
},
},
created() {
this.$bus.$on("addCart", (good) => {
//查找购物车内是否有当前添加的商品,如果有则当前商品数量+1,如果没有则添加到购物车
const ret = this.cart.find((v) => v.id === good.id);
if (!ret) {
//购物车没有数据
this.cart.push(good);
} else {
//ret返回的是当前的对象
ret.count++;
}
});
},
methods: {
setLocalData(newV) {
//计算总课数量
//将数组转换成json字符串存储到本地
localStorage.setItem("cart", JSON.stringify(newV));
},
remove(i) {
if (window.confirm("是否确定删除")) {
this.cart.splice(i, 1);
}
},
handleSub(i) {
let count = this.cart[i].count;
count > 1 ? this.cart[i].count-- : this.remove(i);
},
handleAdd(i) {
this.cart[i].count++;
},
},
computed: {
count() {
//返回购物车中的项
return this.cart.length;
},
activeCount() {
//通过过滤器,将购物车中选中的项筛选出来
return this.cart.filter((v) => v.active).length;
},
total() {
//通过reduce遍历,
return this.cart.reduce((sum, c) => {
if (c.active) {
sum += c.count * c.price;
}
return sum;
}, 0);
},
},
};
</script>
<style scoped>
</style>
对中央事件总线$emit()和$on()有了新的认识,它们并不局限于平行组件之间的通信,感觉只要是组件之间的通信都能用上,学东西还是不能学的太死啊!!!
find
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
语法
arr.find(callback(element,[index],[array]))
callback在数组每一项上执行的函数,接收 3 个参数:
- 当前遍历到的元素。
- 当前遍历到的索引。(可选)
- 数组本身。(可选)
返回值
数组中第一个满足所提供测试函数的元素的值,否则返回 undefined。