购物车开发

购物车可以说是整个购物流程里十分重要的模块了,作为从选购到结算的过渡流程,整体逻辑上还是有点复杂的。下面来给大家展示一下如何使用 Taro 开发一个购物车模块。

页面布局

先上图:

20实战篇 6:购物车开发 - 图1

从这个图便可以看出整个购物车的页面结构了:

  • 以店铺为单位的商品模块,一个店铺的商品放在一块,左边有单选与店铺的全选。
  • 一个吸底的结算栏,也是有全选功能,同时显示总价格,还有去结算的功能。
  • 右上角也有一个吸顶的编辑栏,同时还有优惠券的入口。点击编辑会令购物车变成编辑状态,即可以选择数量、尺码、颜色等。
  • 商品模块后面,下面还会有一个最近浏览的模块,展示最近浏览的商品。

从布局结构就可以看出,整个购物车模块的实现难度主要集中在数据的拉取与显示选购状态与编辑状态的切换商品的编辑逻辑(数量加减,尺码选择,选择与全选等)商品的不同状态(例如补货中是不用被编辑的)。除此之外,还有一些细节的地方需要注意,例如登录态的判断,无商品时的显示等,总而言之,不是一个简单的页面,承载着各种各样的逻辑。

数据拉取与展示

页面比较复杂,我们使用了 Redux 进行开发,以便更好地管理组件之间的状态,同时为了处理异步请求和开发调试,追踪 actions,还用了 redux-thunkredux-logger 中间件。

如果单纯是拉取数据然后渲染出来,那也没什么好讲的。然而,在很多情况下,接口返回的数据和页面显示的数据并不是完全对应的,往往需要再做一层预处理。如下面的例子:

20实战篇 6:购物车开发 - 图2

例如上图红色方框框住的部分,接口返回的数据是:

  1. {
  2. code: 0,
  3. data: {
  4. shopMap: {...}, // 存放购物车里商品的店铺信息的 map
  5. goods: {...}, // 购物车里的商品信息
  6. ...
  7. }
  8. ...
  9. }

对的,购车里的商品店铺和商品是放在两个对象里面的,但视图要求它们要显示在一起。这时候,如果直接将返回的数据存到 store,然后在组件内部 render 的时候东拼西凑,将两者信息匹配,再做显示的话,会显得组件内部的逻辑十分的混乱,不够纯粹。

所以,我个人比较推荐的做法是,在接口返回数据之后,直接将其处理为与页面显示对应的数据,然后再 dispatch 处理后的数据,相当于做了一层拦截,像下面这样:

  1. const data = result.data // result 为接口返回的数据
  2. const cartData = handleCartData(data) // handleCartData 为处理数据的函数
  3. dispatch({type: 'RECEIVE_CART', payload: cartData}) // dispatch 处理过后的函数
  4. ...
  5. // handleCartData 处理后的数据
  6. {
  7. commoditys: [{
  8. shop: {...}, // 商品店铺的信息
  9. goods: {...}, // 对应商品信息
  10. }, ...]
  11. }

可以见到,处理数据的流程在 render 前被拦截处理了,将对应的商品店铺和商品放在了一个对象里。

这样做有如下几个好处:

  • 一个是组件的渲染更纯粹,在组件内部不用再关心如何将数据修改而满足视图要求,只需关心组件本身的逻辑,例如点击事件,用户交互等

  • 二是数据的流动更可控后台数据 ——> 拦截处理 ——> 期望的数据结构 ——> 组件,假如后台返回的数据有变动,我们要做的只是改变 handleCartData 函数里面的逻辑,不用改动组件内部的逻辑。

实际上,不只是后台数据返回的时候,其它数据结构需要变动的时候都可以做一层数据拦截,拦截的时机也可以根据业务逻辑调整,重点是要让组件内部本身不关心数据与视图是否对应,只专注于内部交互的逻辑,这也很符合 React 本身的初衷,数据驱动视图

购物车状态的切换

布局里有说到,右上角有个吸顶的编辑按钮,主要用于切换到购物车的编辑状态。见下图,商品可以加减数量,选择尺寸,底栏变成了删除按钮,大体如同 PC 的购物车,只是交互方式稍微有一些差异。

20实战篇 6:购物车开发 - 图3

这里的难点是视图中同一个位置,在不同的购物车状态下有不同的显示和不同的功能。例如全选,在正常状态下是选择结算,在编辑状态下是选择删除;还有商品图片右边的部分变成了编辑栏。下面用全选的逻辑代码举个例子:

  1. // 点击全选时的函数
  2. checkShopCart = (commodity, e) => {
  3. const {shop, skus} = commodity
  4. const {isEditStatus, fetchCheckCart, fetchInvertCheckCart, inverseCheckDelCart, checkDelCart} = this.props
  5. const skusArr = []
  6. // 重点在这里,isEditStatus 是全局的判断购物车状态值
  7. if (isEditStatus) {
  8. skus.forEach((sku) => { skusArr.push({ id: sku.skuId })})
  9. shop.checkDelAll ? inverseCheckDelCart(skusArr) : checkDelCart(skusArr)
  10. } else {
  11. skus.forEach((sku) => {
  12. // 判断商品是否缺货
  13. if (!sku.isOutOfStock) {
  14. skusArr.push({id: sku.skuId})
  15. }
  16. })
  17. shop.checkAll ? fetchInvertCheckCart(skusArr) : fetchCheckCart(skusArr)
  18. }
  19. }

在整个页面的 redux 里,用了一个 isEditStatus 变量,用于判断当前的页面状态,然后通过 connect 注入到该页面中。所以在用户点击了这个按钮后,会有一个对 isEditStatus 值的判断,从而执行不同的逻辑。聪明的你应该还会发现,还有一个对 checkDelAllcheckAll 的值的判断,这里是用于判断当前店铺是否被全选,然后执行商品的全选 or 反选逻辑。

除了事件点击后会根据页面当前状态而发生不同的逻辑以外,界面也会作出相应的变化,如:

  1. // 伪代码
  2. render() {
  3. const {isEditStatus} = this.props
  4. return <View>
  5. {!isEditStatus ? < 非编辑态 /> : < 编辑态 />}
  6. </View>
  7. }

总的来说,就是根据不同的状态做不同的事情,与其说是难,倒不如说是繁琐。因为里面涉及的状态,操作会比较多,所以需要细心对应好才能不出错。

购物车的操作逻辑

所谓的操作逻辑,指的就是加车,改变尺寸,删除,改变数量等。实际上,实现这些逻辑其实没有什么很通用的做法,都是跟业务强耦合的,在前端的角度,就是和后端的接口及返回的数据是强耦合的。因为后端接口是如何设计的,返回怎样的数据就直接会决定你的页面逻辑是怎样的。

具体到我们这个项目里,每进行一次操作,都需要请求一次接口,然后返回全新的购物车数据,重新渲染。结合上面的数据的拉取与展示部分,可以得到,每次操作后的完整流程是:操作购物车——> 拉取接口 ——> 后台数据 ——> 拦截处理 ——> 期望的数据结构 ——> 组件渲染

可以看出,这个流程的每一环都是相互独立的,互不影响,这样就很大程度地减少了整个购物车逻辑的复杂性。例如我们要增加数量,或者是减少数量等,都只是拉取不同的接口就好了,后面的数据处理,渲染等并不用作其它的处理,都是一样的逻辑。

对于组件渲染的部分,要做的就是只是确保什么状态对应什么视图,例如这个按钮是否被选中,是否有缺货的显示等等。

另外还有值得注意的是,改变尺寸这个操作是会有一个弹窗的,用来选择需要哪种尺寸,如下图:

20实战篇 6:购物车开发 - 图4

这里的数据拉取其实脱离上述的操作流程的,或者说,是属于另一个操作流程,不是购物车渲染这块。但是整体的处理和逻辑是类似的,这里就不细说了(这里有个坑,具体可以看《Taro 开发说明与注意事项》章节),重点是要了解这种思想,把页面交互,数据处理与渲染逻辑尽可能地解耦,这样才可以让我们符合数据驱动视图的思想。

其它细节

开发一个购物车页面,除了主要的购物车的显示与操作,还有很多其它细节是需要注意的:

  • 例如未登录时,顶部会有一个去登录的吸顶栏,点击优惠券和底部去结算时都是会跳到登录页面
  • 没有商品时,编辑按钮是不会出现的
  • 加车的数量最多是 200,超过后会自动变为 200,同时 1 个数量再减意味着是 0,即是删除
  • 选择了商品后,底部的结算(删除)按钮会变色,同时会显示数量,超过 99 会显示 99+
  • ……

这些细节相关的都不是很难,而是有点繁琐,重点还是之前所说的,要耐心,细心,具体还是需要在开发中慢慢积累,正所谓熟能生巧。

小结

本文从页面布局、数据拉取与切换、购物车状态的切换、购物车的操作逻辑和其他细节这 5个方面阐述了如何使用 Taro 开发购物车。主要是从原理,架构的角度去剖析一个购物车页面的开发,并没有过于针对某些代码细节。一个是代码太长太多,全部贴出来解释也不现实;二是代码始终是与业务强耦合的,而思想,方法才是可以不断复用的东西,授人以鱼不如授人以渔。