文章目录
一、概要
实现购物车的功能,主要包括自己订单显示,一键全选商品、购买商品总价格、购买商品总数量等增删改的功能。为了方便自己对主要功能的编码,所以对于样式直接利用demo-cart进行操作,不用自己从零开始写。
资料下载:购物车案例代码
技术:vue2+axios+bootstrap
二、整体架构流程
首先展示页面效果图:
然后页面结构分析:
最终根据页面结构提取出整体的数据流向图:
在书写这个小demo的时候,个人觉得会有一些难点,总结如下:
- 由于每个组件里面的属性值比较多,很容易搞混,有时候就会感觉自己和视频代码写得一模一样结果却得不到想要的效果。原因有可能就是把字段写错啦
- 得明确每个组件有自己的数据,各个组件之间的数据都有流动方向的,从组件结构分析数据流动方向是利用3种通信方式中的哪一种。这里值得注意的是孙子向爷爷传递数据的时候使用兄弟组件之间的通信比通过子传父通信更好一点,原因是因为如果是子传父的话需要数据传递两次:子传父,父传爷,相对来说这就比利用兄弟间通信方式要复杂一点。
- 还有一个难点就是对于数组操作的4个函数的使用:some、every、forEach、reduce,利用箭头函数的简写,自己水平还有限没系统学过JS语法。
- 由于刚开始学习,代码比较多,特别是样式我感觉是最难的,当出现了错误不能很快找到解决问题。所以在刚开始的时候不必要注重样式问题,目的就是先完成功能需求。
三、功能实现
自己在独立完成的时候,总是受到上述分析的困难,导致不能独立完成购物车demo的开发。因此为了解决上述问题,我在每次要写一个功能的时候时,先知道想要的页面效果,然后画出数据流向图,最后根据数据流向图进行编码。个人对这个demo的理解分为以下几个部分。每一部分的讲解又分为效果图、涉及的知识点、数据流向图、代码。
3.1 注册组件
- 效果图:
- 注册3个组件:Header Goods Footer。注册一个组件分为3部分:import导入、添加进components组件里面、在template中使用组件。需要值得注意的是别把组件名字写错了。
3.2 导入axios
- 安装组件:npm i axios -S
- 引入组件: import axios from ‘axios’。 这是一种局部引入,哪里需要使用axios哪里就需要引入
- 调用axios发送请求:对于这部分我个人觉得因为它的接口不稳定(容易报502 bad gateway错误),而且接口返回的数据属性有的名称和原来视频名称不一样,还有的属性缺少。对于初学者不友好,所以我自己再写的时候就把数据固定存储在list里面。可以使用下面这个接口练习发送请求接受数据并存储在app组件的list这个知识点。
https://applet-base-api-t.itheima.net/api/goods
- 页面一加载就要显示,所以在created()里面调用initCartList()方法。
3.3 渲染Goods组件
- 效果图:这部分效果图可以分为两部分——封装title和price、封装id和state
封装title和price:
封装id和state:
- 整体的数据流
这部分需要分析一下每个属性对应于页面效果上的那一部分的显示,数据的流向。刚开始时只有app组件有数据,但是想要让Goods组件显示app组件的数据,所以利用props进行父传子通信。这里需要区分一下白色属性和绿色属性,其实在实现这部分功能只需要白色属性就可以啦,绿色属性是为了实现购买商品数量的,在下面的部分会讲解到。 - 代码:
<Goods
v-for="item in list"
:key="item.id"
:title="item.goods_name"
:price="item.goods_price"
:state="item.goods_state"
:id="item.id"
></Goods>
- 值得注意的是这里为啥需要id属性,主要是为了定位购买的是哪一个商品,因为复选框决定了商品是否需要购买。所以每个Goods组件都需要一个唯一的id,来分辨是哪类商品。
3.4 封装Goods组件state状态
当用户点击了Goods组件的复选框的时候其state值会改变,得注意区分Goods组件的state值和app组件的goods_state值是两块区域不同的数据,在没有函数进行处理的时候Goods组件数据的改变不会影响app组件的数据,虽然前面使用了父传子,但数据流动是有方向性的,a改变b会改变,但是b改变a不会改变。现在想要b改变后a也改变,所以就需要子传父通信。
- 数据流向图:
- Goods组件代码:
当state的值发生改变就得触发子传父的事件
<input
type="checkbox"
class="custom-control-input"
:id="'cb' + id"
:checked="state"
@change="stateChange"
/>
具体事件:发送的时候数据包含id和state值
stateChange(e) {
console.log("----------stateChange----------------");
console.log(e.target.checked);
// 传递过去的数据应该是有id和state两个属性;
// id确定是那个Goods, state确定状态 但是这个id如何得到了 傻逼了直接输入this.id即可
// console.log(this.id);
// 封装传递过去的数据 {id:id, value:state}
const state = e.target.checked;
const obj = { id: this.id, value: state };
// Goods子组件发送给App父组件
this.$emit("state-change", obj);
},
},
- 现在Goods组件已经将数据传递给app组件了,所以app组件需要接受数据
在app.vue的Goods标签接受传递过来的数据
<!-- 2.商品详情 -->
<Goods
v-for="item in list"
:key="item.id"
:title="item.goods_name"
:price="item.goods_price"
:state="item.goods_state"
:id="item.id"
:count="item.goods_count"
@state-change="getNewState"
></Goods>
具体实现事件如下:
getNewState(e) {
// 自己犹豫了一下是否要加参数 因为@state-change="stateChange"并没有添加参数 但是子组件是传递参数的
console.log("接收子组件传递过来的数据");
console.log(e);
// 参数e的数据格式:{id:id, value:state}
this.list.some((item) => {
if (item.id === e.id) {
item.goods_state = e.value;
return true; // 这里的return true啥意思
}
});
},
3.5 Goods点击Footer监听是否全选,子传父修改App中的list的state状态
上述3.4的操作以后将达到Goods组件的state改变则app组件的goods_state也会改变的效果。现在需要Footer组件判断list数组中当全部的数据都被选中以后,则勾选全选按钮。app组件和Footer组件是父子关系,app组件的数据满足一定条件后Goods组件会做出一定的响应,又是父传子。为了能够判定Goods组件的状态所以需要定义一个属性isfull。对于app的满足一定条件可以使用计算属性来表示
-
数据流向图:
-
计算属性fullstate:当app的list数据被全选则返回true,否则返回false,调用数组的every方法
// 计算属性
computed: {
fullstate() {
return this.list.every((item) => item.goods_state);
}
}
3.利用props进行父传子,在Footer标签上绑定属性值,在Footer中定义isfull的props属性和input标签绑定isfull值
<input type="checkbox" class="custom-control-input" id="cbFull" :checked="isfull" />
<!-- 3.尾部 -->
<Footer :isfull="fullstate" ></Footer>
3.6 添加counter组件,明确商品数量的两个流动方向
添加商品增加和减少的组件。还得分析app组件、Goods组件、Counter组件的数据流,重点是数据的双向流动。
首先APP->Goos->Counter,这里面设计两次数据传递都是父传子,这里数据绑定和封装title等属性是同样的道理,需要注意的是别把属性名称写错了;然后Counter->App这里是利用兄弟通信eventBus.js。
具体原因视频中也有分析,简单来说就是为了方便只需要一次数据传递。
- 实现效果:
- 数据流:
3.7 商品数量的数据传递
对于绿色的数据流前面3.4节已经讲解过了,接下来重点分析一下子传爷使用eventBus.js进行通信的方式。Counter组件作为发送方,利用bus. e m i t ( ) ; A p p 组件作为接收方在 c r e a t e d ( ) 里面使用 b u s . emit();App组件作为接收方在created()里面使用bus. emit();App组件作为接收方在created()里面使用bus.on()进行数据接收。并且在发送的数据格式应该包括商品确定编号和数量:id和num。将其封装为一个对象进行传递
- Counter组件发送方:绑定add和sub函数对num进行加减,这里需要注意num是prop只读属性所以要写成 this.num+1
methods: {
// 点击按钮,数值 +1
add() {
// 要发送给 App 的数据格式为 { id, value }
// 其中,id 是商品的 id; value 是商品最新的购买数量
const obj = { id: this.id, value: this.num + 1 };
// 要做的事情:通过 EventBus 把 obj 对象,发送给 App.vue 组件
bus.$emit("share", obj);
},
sub() {
if (this.num - 1 === 0) return;
// 要发送给 App 的数据格式为 { id, value }
// 其中,id 是商品的 id; value 是商品最新的购买数量
const obj = { id: this.id, value: this.num - 1 };
// 要做的事情:通过 EventBus 把 obj 对象,发送给 App.vue 组件
bus.$emit("share", obj);
},
},
- App组件数据接收方:created方法获取数据,发送方和接收方均需要引入eventBus.js
created() {
this.initCartList();
bus.$on("share", (e) => {
console.log(e);
this.list.some((item) => {
if (item.id == e.id) {
item.goods_count = e.value;
return true;
}
});
});
},
3.8 全选和全不选功能
需要实现在Footer组件中取消全选则Goods组件中选择的商品都会被取消,涉及到子传父通信。
- 数据流
- Footer组件中当input标签有变化就会触发子传父,用this.$emit() 同样也是需要封装要发送的数据: id和state
<input
type="checkbox"
class="custom-control-input"
id="cbFull"
:checked="isfull"
@change="fullChange"
/>
<label class="custom-control-label" for="cbFull" :checked="isfull"
>全选</label
>
methods: {
fullChange(e) {
console.log("触发fullChange事件");
this.$emit("full-change", e.target.checked);
},
},
- App数据接收方:Footer标签绑定函数 函数进行接收数据
<!-- 3.尾部 -->
<Footer
:isfull="fullstate"
:amount="amt"
:all="countAll"
@full-change="getFullState"
></Footer>
getFullState(e) {
console.log("接收到子组件传过来的数据:" + e);
this.list.forEach((item) => {
item.goods_state = e;
});
},
},
遇到的报错
这里记录一下自己在独立完成的过程中遇到的错误
例如:
- 写错了组件名
- 将属性名state写成了status
- 在标签中的函数名和methods中的函数名不一致
- 对props数据进行修改,导致出现许多警告
小结
总的来说这个案例的逻辑并不复杂,但是要自己独立完成还是很考验耐心和基本功的。本人基本功较差,觉得js的那4个函数不好搞。这篇博客侧重于数据流向分析,对于代码实现可以参考黑马教程或者其它优秀的博客。总体来说涉及到的知识点总结如下:
- 三种通信方式:这里需要注意一下的就是那个商品数量是通过eventBus.js进行的通信
- axios请求的发送
- vue常见那6种命令的使用
- js种对于数组的4种方法