🌟Set集合新特性,快速实现一个商品SKU(单品)规格选择器

1,169 阅读5分钟

动画.gif

前言

在电商平台的开发中,商品SKU选择器是一个看似简单却蕴含复杂逻辑的组件。传统的实现方式往往依赖于大量的条件判断和嵌套循环,不仅代码臃肿,而且在处理多规格商品时容易产生性能问题和逻辑漏洞。本文将介绍一种基于数学集合理论的创新实现方式,下文将使用ES6Set构造函数及其新特性intersection()方法,快速构建一个优雅的SKU选择器。

集合的特性

首先简单了解一下集合的特性。集合的特性具有确定性、互异性、无序性。利用集合我们可以不用关注已选中的规格中是否具有相同的规格属性、选择规格的顺序等不确定性,可以很大程度上为我们省去一大部分的计算工作。集合中的交集概念,大家应该都了解吧,这里就不多加赘述了。

一个小栗子,图文展示实现原理

假设我们有一个商品名为 "智能手机" ,该手机具有三种规格:“ 颜色(红色、紫色)、套餐(套餐一、套餐二)、内存(64G、128G)”,且当前可选单品有三个,单品一(红色, 套餐一、64G)、单品二(紫色, 套餐一、128G)、单品三(紫色, 套餐二、128G)。

我们以集合的视角来直观地描绘题中的关键信息:

简单例子.png

上图中主要描绘了单个属性是否可选的判断流程,关键在于把属性自身和当前已选中的属性组成一个新集合,然后遍历单品清单,新集合逐个和单品集合进行取交集,如果存在一个交集长度和新集合长度相等,则代表新集合可以和单品集合完全相交,即该属性是可选的,反之则不可选。

核心实现代码

页面结构渲染

<!-- 渲染规格选择器 -->
<view class="item" v-for="(item, index) in specList" :key="index">
  <view class="tag-list-title">{{item.title}}</view>
  <view class="tag-list">
    <view class="tag" v-for="(name, j) in item.list" :key="j">
      <uni-tag inverted :text="name" :type="isActive(item.title, name)"
        :disabled="!isAble(item.title, name)" @click="onChange(item.title, name)"
      ></uni-tag>
    </view>
  </view>
</view>
// 展示的规格选择器数据结构
specList: [{
    title: "颜色",
    list: ["红色", "紫色", "白色", "黑色"]
  },
  {
    title: "套餐",
    list: ["套餐一", "套餐二", "套餐三", "套餐四"]
  },
  {
    title: "内存",
    list: ["64G", "128G", "256G"]
  }
],
// 商品数据结构
data: {
  id: 6262,
  title: '智能手机',
  icon: '/static/logo.png',
  skuList: [
    {
      id: 1608188117178,
      specs: ["红色", "套餐一", "128G"],
      stock: 1,
      price: 10
    },
    {
      id: 1608188117179,
      specs: ["红色", "套餐一", "256G"],
      stock: 2,
      price: 10
    },
  ]
}

业务逻辑处理

// 当前选中的规格映射
selectedSpecificationObj: {
  // '颜色': '红色',
  // '套餐': '套餐一',
  // '内存': '64G'
},

// 判断当前规格是否可选方法
isAble(key, value) {
  // 复制一份已选择的规格
  const currentAndSelectedSpecificationObj = {...this.selectedSpecificationObj}
  // 把当前判断的值替换到相同规格下
  currentAndSelectedSpecificationObj[key] = value
  // 创建集合,看是否具有sku和当前选中的规格相交。如何能全部相交,则代表当前值可以选择
  const currentSpecificationSet = new Set(Object.values(currentAndSelectedSpecificationObj).filter(Boolean))
  const skus = this.data.skuList
  for(let i = 0; i < skus.length; i++) {
    const sku = skus[i]
    const specSet = new Set(sku.specs)
    // 判断当前规格和选中的规格集合是否全部相交
    if(currentSpecificationSet.size === specSet.intersection(currentSpecificationSet).size) {
      return true
    }
  }
  return false
},

// 获取当前规格是否选中状态
isActive(key, value) {
  return this.selectedSpecificationObj[key] === value ? 'warning' : ''
},

Set实例方法intersection

intersection()Set原型上的一个方法,该方法可以接收一个Set实例参数,并返回一个包含此集合和给定集合中元素的新集合,也就是返回两个集合的交集。

查看MDN文档详细介绍

SKU选择器完整实现图解

商品SKU选择器.png

  1. 初始化时,每个属性默认会执行一遍isAble()方法,对自身进行初始化。计算出自身可选或不可选。
  2. 初始化完成,用户选择可选的属性并保存到已选中的属性当中。
  3. 已选中属性改变,自动触发isAble()方法重新计算。在方法中copy一份已选中属性,并把已选中属性相同规格的key值替换为参数的属性值,最后通过组合的对象创建一个新集合。
  4. 遍历单品集合。如果存在,新集合的长度=新集合和单品集合取交集的长度,则单品中存在该属性与已选中属性组合的单品,即该属性可选,反之则不可选。
  5. isAble()方法执行结束并返回该属性是否可选。

执行性能优化

使用Map对已计算过的规格组合进行缓存,已计算过的规格组合作为key,值为计算的结果(truefalse)。在执行计算前,先把当前组合的规格keyMap中取计算过的值,如果存在直接返回该结果,如果不存在,则正常遍历所有单品进行计算。

// 缓存已经做过判断的规格
cacheSelectedSpecificationMap: new Map()

// 判断当前规格是否可选方法
isAble(key, value) {
  // 复制一份已选择的规格
  const currentAndSelectedSpecificationObj = {...this.selectedSpecificationObj}
  // 把当前判断的值替换到相同规格下
  currentAndSelectedSpecificationObj[key] = value
  // 缓存的对象字符串key值
  const cacheKey = JSON.stringify(currentAndSelectedSpecificationObj)
  // 已缓存,则使用缓存中的值
  if(this.cacheSelectedSpecificationMap.has(cacheKey)) {
    return this.cacheSelectedSpecificationMap.get(cacheKey)
  }
  // console.log('isAble')
  // 创建集合,看是否具有sku和当前选中的规格相交。如何能全部相交,则代表当前值可以选择
  const currentSpecificationSet = new Set(Object.values(currentAndSelectedSpecificationObj).filter(Boolean))
  const skus = this.data.skuList
  for(let i = 0; i < skus.length; i++) {
    const sku = skus[i]
    const specSet = new Set(sku.specs)
    // 判断当前规格和选中的规格集合是否全部相交
    if(currentSpecificationSet.size === specSet.intersection(currentSpecificationSet).size) {
      // 缓存计算结果
      this.cacheSelectedSpecificationMap.set(cacheKey, true)
      return true
    }
  }
  // 缓存计算结果
  this.cacheSelectedSpecificationMap.set(cacheKey, false)
  return false
},

总结

上文主要通过单个属性自行和已选中的属性进行组合,使用Set(集合)的新特性intersection(取交集)方法,使组合后的新集合与可选单品的属性集合进行取交集,最后取交集长度组合的集合长度进行比较,如果长度相等则代表该属性可选。其中关键在于正确记录已选中规格的数据,每个属性的判断都得依托已选中的规格。

有什么问题,欢迎大家评论区进行讨论,谢谢大家!