SKU組件(React版)

SKU組件(React版)

這里的一些邏輯還是需要自己再優(yōu)化一下的

起因

今天看掘金的時候看到前端SKU算法實(shí)現(xiàn),因?yàn)楣疽灿猩婕暗絊KU的業(yè)務(wù),記錄一下自己寫SKU的一個例子吧,剛好他有提供后端的API接口數(shù)據(jù),mock一下干起來,但是在做的時候還是有很多問題的,這里做一下記錄

實(shí)現(xiàn)效果

Peek 2020-03-16 12-08.gif

mock數(shù)據(jù)

export const simulatedSku = {
  id: 2,
  title: "林間有風(fēng)自營針織衫",
  subtitle: "瓜瓜設(shè)計,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組件,便于狀態(tài)的統(tǒng)一管理

  • SKU Card的實(shí)現(xiàn),其實(shí)很簡單,就是在激活的時候和非激活的時候通過狀態(tài)位,修改css屬性,另外onChange的時候?qū)⒒貞?yīng)的SKU的value進(jìn)行傳遞
    • value: sku對應(yīng)的sku_id
    • label: 顯示的sku名稱
    • onChange: sku發(fā)生變化的時候的回調(diào)函數(shù)
    • disabled: 禁用標(biāo)志位
    • 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的狀態(tài),類似于RadioGroup, CheckboxGroup其實(shí)都可以模仿這種封裝的思路
    • 利用props.children獲取各個子元素的ReactElement對象,之后通過cloneElement將父組件內(nèi)管理狀態(tài)的onChange方法進(jìn)行注入(類似于HOC那種感覺),將子組件的activate和onChange方法通過父組件進(jìn)行管理
    • 封裝一些其他自己要用的屬性
    • 大功告成
// 定義了Empty,這個Empty對空的時候進(jìn)行設(shè)置
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組件實(shí)現(xiàn)的思路分析

  • 從數(shù)據(jù)來看,每個商品(SPU)中包含多個SKU,所以要將多個SKU分別提出來整理成這個樣子,就是想sku進(jìn)行歸類
選區(qū)_059.png
  • 點(diǎn)擊選中某個SKU之后,將選中的SKU的id作為篩選列表中的值,我們需要遍歷整個商品列表,篩選出在商品列表中所有滿足篩選條件的商品
  • 通過在滿足條件的商品列表中進(jìn)行遍歷,得到剩下可選的sku,其余的將sku中的disabled設(shè)為true即不能被選擇
// 代碼中的幾個關(guān)鍵變量
// 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是如何改變的,并且調(diào)整對應(yīng)的aviableSku
useEffect(() => {
    // 利用useRef記錄上一次選擇sku的狀態(tài)
    if (prevSku.current) {
        // 找到哪一個SKU的值發(fā)生了變化
        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,說明原來選中,現(xiàn)在取消選中場景
                    ? index !== cIndex && item !== Empty
                    // 說明Item是有限定值的
                    : item !== Empty
                ) {
                    // 將限定值保存在otherCondition中
                    // 記錄現(xiàn)在的限定狀態(tài)
                    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]);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容