SKU组件(React版)

作者: 我是皮蛋 | 来源:发表于2020-03-16 18:26 被阅读0次

SKU组件(React版)

这里的一些逻辑还是需要自己再优化一下的

起因

今天看掘金的时候看到前端SKU算法实现,因为公司也有涉及到SKU的业务,记录一下自己写SKU的一个例子吧,刚好他有提供后端的API接口数据,mock一下干起来,但是在做的时候还是有很多问题的,这里做一下记录

实现效果

Peek 2020-03-16 12-08.gif

mock数据

export const simulatedSku = {
  id: 2,
  title: "林间有风自营针织衫",
  subtitle: "瓜瓜设计,3件包邮",
  category_id: 12,
  root_category_id: 2,
  price: "77.00",
  img: "",
  for_theme_img: "",
  description: null,
  discount_price: "62.00",
  tags: "包邮$热门",
  is_test: true,
  online: true,
  sku_list: [
    {
      id: 2,
      price: 77.76,
      discount_price: null,
      online: true,
      img: "",
      title: "金属灰·七龙珠",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 45,
          value: "金属灰"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 9,
          value: "七龙珠"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 14,
          value: "小号 S"
        }
      ],
      code: "2$1-45#3-9#4-14",
      stock: 5
    },
    {
      id: 3,
      price: 66,
      discount_price: 59,
      online: true,
      img: "",
      title: "青芒色·灌篮高手",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 42,
          value: "青芒色"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 10,
          value: "灌篮高手"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 15,
          value: "中号 M"
        }
      ],
      code: "2$1-42#3-10#4-15",
      stock: 999
    },
    {
      id: 3,
      price: 66,
      discount_price: 59,
      online: true,
      img: "",
      title: "橘黄色·灌篮高手",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 44,
          value: "橘黄色"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 10,
          value: "灌篮高手"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 15,
          value: "中号 M"
        }
      ],
      code: "2$1-42#3-10#4-15",
      stock: 999
    },
    {
      id: 4,
      price: 88,
      discount_price: null,
      online: true,
      img: "",
      title: "青芒色·圣斗士",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 42,
          value: "青芒色"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 11,
          value: "圣斗士"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 16,
          value: "大号  L"
        }
      ],
      code: "2$1-42#3-11#4-16",
      stock: 8
    },
    {
      id: 5,
      price: 77,
      discount_price: 59,
      online: true,
      img:
        "http://i1.sleeve.7yue.pro/assets/09f32ac8-1af4-4424-b221-44b10bd0986e.png",
      title: "橘黄色·七龙珠",
      spu_id: 2,
      category_id: 17,
      root_category_id: 3,
      specs: [
        {
          key_id: 1,
          key: "颜色",
          value_id: 44,
          value: "橘黄色"
        },
        {
          key_id: 3,
          key: "图案",
          value_id: 9,
          value: "七龙珠"
        },
        {
          key_id: 4,
          key: "尺码",
          value_id: 14,
          value: "小号 S"
        }
      ],
      code: "2$1-44#3-9#4-14",
      stock: 7
    }
  ],
  spu_img_list: [
    {
      id: 165,
      img:
        "http://i1.sleeve.7yue.pro/assets/5605cd6c-f869-46db-afe6-755b61a0122a.png",
      spu_id: 2
    }
  ],
  spu_detail_img_list: [
    {
      id: 24,
      img: "http://i2.sleeve.7yue.pro/n4.png",
      spu_id: 2,
      index: 1
    }
  ],
  sketch_spec_id: 1,
  default_sku_id: 2
};

简单的封装一个SKUCard和SKUGroup

类似于RadioGroup和Radio,我们先封装一个简单的SKU Group和SKU组件,便于状态的统一管理

  • SKU Card的实现,其实很简单,就是在激活的时候和非激活的时候通过状态位,修改css属性,另外onChange的时候将回应的SKU的value进行传递
    • value: sku对应的sku_id
    • label: 显示的sku名称
    • onChange: sku发生变化的时候的回调函数
    • disabled: 禁用标志位
    • activate: 是否为激活模式
export const SkuCard = props => {
  const { value, label, onChange, disabled, activate, style } = props;
  const [innerActive, setInnerActive] = useState(activate ?? false);

  const handleChange = value => () => {
    if (!disabled) {
      onChange?.(value, !innerActive);
      setInnerActive(!innerActive);
    }
  };

  return (
    <div
      className={
        disabled ?? false
          ? "disabled"
          : activate ?? innerActive
          ? "activate"
          : "normal"
      }
      onClick={handleChange(value)}
      style={{ ...(style ?? {}) }}
    >
      {label}
    </div>
  );
};
  • SKU Group: 集中管理SKU的状态,类似于RadioGroup, CheckboxGroup其实都可以模仿这种封装的思路
    • 利用props.children获取各个子元素的ReactElement对象,之后通过cloneElement将父组件内管理状态的onChange方法进行注入(类似于HOC那种感觉),将子组件的activate和onChange方法通过父组件进行管理
    • 封装一些其他自己要用的属性
    • 大功告成
// 定义了Empty,这个Empty对空的时候进行设置
export const Empty = Symbol("empty");

export const SkuGroup = props => {
  const { value, onChange, skuName } = props;

  const [selected, setSelected] = useState(value);
  const { children } = props;

  const _onChange = (value, activate) => {
    const _value = !activate && selected === value ? Empty : value;
    setSelected(_value);
    onChange?.(_value);
  };

  const renderGroupChild = (child, index) => {
    const { props: childProps } = child;

    return React.cloneElement(child, {
      ...childProps,
      onChange: _onChange,
      activate: childProps.value === selected,
      key: `create-${index}`,
      style: {
        ...(childProps?.style ?? {}),
        marginLeft: index === 0 ? 0 : "20px"
      }
    });
  };

  return (
    <div className="skuGroup">
      {skuName && <div className="labelName">{skuName}</div>}
      {children.map((child, index) => {
        return child?.type === SkuCard ? renderGroupChild(child, index) : child;
      })}
    </div>
  );
};

SKU组件实现的思路分析

  • 从数据来看,每个商品(SPU)中包含多个SKU,所以要将多个SKU分别提出来整理成这个样子,就是想sku进行归类
选区_059.png
  • 点击选中某个SKU之后,将选中的SKU的id作为筛选列表中的值,我们需要遍历整个商品列表,筛选出在商品列表中所有满足筛选条件的商品
  • 通过在满足条件的商品列表中进行遍历,得到剩下可选的sku,其余的将sku中的disabled设为true即不能被选择
// 代码中的几个关键变量
// skuList: 商品拥有的所有sku组合的型号(SPU中的所有商品类型)
// sku: 需要显示的sku card
// selectSku: radio显示选中值的[1, 2, 3]

// 初始化的时候aviableSku就是所有的商品类目
const _getSku = (aviableSku = []) => {
    const _sku = {};
    const _aviableSku = {};

    // 得到目前可以选择的所有商品的sku
    aviableSku.forEach(item => {
        item.forEach(x => {
            const key = JSON.stringify({ key_id: x.key_id, key: x.key });

            const value = {
                value_id: x.value_id,
                value: x.value,
                disabled: false
            };

            _aviableSku[key]
                ? _aviableSku[key].some(z => z.value_id === x.value_id)
                ? null
            : _aviableSku[key].push(value)
            : (_aviableSku[key] = [value]);
        });
    });

    // 将SKU中所有不满足aviableSku的东西diabled掉
    skuList.forEach(item => {
        // 每个商品
        item.forEach((x, i) => {
            // 商品下的每个sku
            const key = JSON.stringify({ key_id: x.key_id, key: x.key });
            const value = {
                value_id: x.value_id,
                value: x.value,
                disabled: !_aviableSku[key].some(item => item.value_id === x.value_id)
            };

            _sku[key]
                ? _sku[key].some(z => z.value_id === x.value_id)
                ? null
            : _sku[key].push(value)
            : (_sku[key] = [value]);
        });
    });

    setMySku(_sku);
};
  • 在选择sku的时候,我们需要确定这个sku是如何改变的,并且调整对应的aviableSku
useEffect(() => {
    // 利用useRef记录上一次选择sku的状态
    if (prevSku.current) {
        // 找到哪一个SKU的值发生了变化
        const cIndex = findChangeIndex(prevSku.current, selectSku);
        if (cIndex !== -1) {
            const changeValue = selectSku[cIndex];
            let otherCondition = {};

            const keys = Object.keys(sku);
            selectSku.forEach((item, index) => {
                if (
                    changeValue === Empty
                    // 改变值为Empty,说明原来选中,现在取消选中场景
                    ? index !== cIndex && item !== Empty
                    // 说明Item是有限定值的
                    : item !== Empty
                ) {
                    // 将限定值保存在otherCondition中
                    // 记录现在的限定状态
                    const key_id = JSON.parse(keys[index])["key_id"];
                    otherCondition[key_id]?.push(item) ??
                        (otherCondition[key_id] = [item]);
                }
            });

            // 通过限定矩阵的值挑选出满足条件的商品类别
            const aviableSku = skuList.filter(good => {
                const aviableGood = good.map(sku => {
                    const isInOther = otherCondition[sku.key_id];
                    return isInOther !== undefined
                        ? isInOther.includes(sku.value_id)
                    : true;
                });

                return aviableGood.every(item => item);
            });

            _getSku(aviableSku);
        }
    } else {
        _getSku(skuList);
    }

    prevSku.current = selectSku;
}, [selectSku]);

相关文章

  • SKU组件(React版)

    SKU组件(React版) 这里的一些逻辑还是需要自己再优化一下的 起因 今天看掘金的时候看到前端SKU算法实现,...

  • react重点

    React组件的生命周期(React16.4版本) 1.constructor方法 用于组件内部的state初始化...

  • React基础

    React包含react元素和react组件 react元素 react组件 react组件分为函数组件和类组件 ...

  • PureComponent的使用指南

    前言 React在15.3版本以后出现了PureComponent组件,作用是减少继承该组件的组件渲染的次数从而达...

  • vue sku组件

    https://github.com/rossroma/vue-sku

  • useState和useEffect

    Hooks Hooks是React16.8.0版本推出的api,用来解决函数组件中功能不足的问题 组件:无状态组件...

  • react-native (三 - 下) : react-nav

    react-navigation 它是facebook官方推荐的代替 < 0.43.0版本Navigator组件的...

  • 标注组件-react版

    组件支持标注类型:1、图像 — 浏览、标注集合展示2、图像分类 — 支持对图像进行分类标注3、图像检测 — 支持对...

  • React Navigation的简单使用(一)

    说明:从0.44版本开始,Navigator被从react native的核心组件库中剥离到了一个名为react-...

  • React Native UNMET PEER DEPENDEN

    从0.44版本开始,Navigator被从react native的核心组件库中剥离到了一个名为react-nat...

网友评论

    本文标题:SKU组件(React版)

    本文链接:https://www.haomeiwen.com/subject/yjznehtx.html