SKU組件(React版)

SKU組件(React版)

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

起因

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

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

Peek 2020-03-16 12-08.gif

mock數(shù)據(jù)

export const simulatedSku = {
  id: 2,
  title: "林間有風(fēng)自營(yíng)針織衫",
  subtitle: "瓜瓜設(shè)計(jì)忆家,3件包郵",
  category_id: 12,
  root_category_id: 2,
  price: "77.00",
  img: "",
  for_theme_img: "",
  description: null,
  discount_price: "62.00",
  tags: "包郵$熱門(mén)",
  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: "小號(hào) 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: "中號(hào) 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: "中號(hào) 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: "大號(hào)  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: "小號(hào) 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
};

簡(jiǎn)單的封裝一個(gè)SKUCard和SKUGroup

類(lèi)似于RadioGroup和Radio,我們先封裝一個(gè)簡(jiǎn)單的SKU Group和SKU組件抑钟,便于狀態(tài)的統(tǒng)一管理

  • SKU Card的實(shí)現(xiàn),其實(shí)很簡(jiǎn)單,就是在激活的時(shí)候和非激活的時(shí)候通過(guò)狀態(tài)位,修改css屬性,另外onChange的時(shí)候?qū)⒒貞?yīng)的SKU的value進(jìn)行傳遞
    • value: sku對(duì)應(yīng)的sku_id
    • label: 顯示的sku名稱(chēng)
    • onChange: sku發(fā)生變化的時(shí)候的回調(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),類(lèi)似于RadioGroup, CheckboxGroup其實(shí)都可以模仿這種封裝的思路
    • 利用props.children獲取各個(gè)子元素的ReactElement對(duì)象雀监,之后通過(guò)cloneElement將父組件內(nèi)管理狀態(tài)的onChange方法進(jìn)行注入(類(lèi)似于HOC那種感覺(jué))双吆,將子組件的activate和onChange方法通過(guò)父組件進(jìn)行管理
    • 封裝一些其他自己要用的屬性
    • 大功告成
// 定義了Empty,這個(gè)Empty對(duì)空的時(shí)候進(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ù)來(lái)看,每個(gè)商品(SPU)中包含多個(gè)SKU,所以要將多個(gè)SKU分別提出來(lái)整理成這個(gè)樣子,就是想sku進(jìn)行歸類(lèi)
選區(qū)_059.png
  • 點(diǎn)擊選中某個(gè)SKU之后,將選中的SKU的id作為篩選列表中的值,我們需要遍歷整個(gè)商品列表,篩選出在商品列表中所有滿(mǎn)足篩選條件的商品
  • 通過(guò)在滿(mǎn)足條件的商品列表中進(jìn)行遍歷,得到剩下可選的sku,其余的將sku中的disabled設(shè)為true即不能被選擇
// 代碼中的幾個(gè)關(guān)鍵變量
// skuList: 商品擁有的所有sku組合的型號(hào)(SPU中的所有商品類(lèi)型)
// sku: 需要顯示的sku card
// selectSku: radio顯示選中值的[1, 2, 3]

// 初始化的時(shí)候aviableSku就是所有的商品類(lèi)目
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中所有不滿(mǎn)足aviableSku的東西diabled掉
    skuList.forEach(item => {
        // 每個(gè)商品
        item.forEach((x, i) => {
            // 商品下的每個(gè)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的時(shí)候,我們需要確定這個(gè)sku是如何改變的,并且調(diào)整對(duì)應(yīng)的aviableSku
useEffect(() => {
    // 利用useRef記錄上一次選擇sku的狀態(tài)
    if (prevSku.current) {
        // 找到哪一個(gè)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,說(shuō)明原來(lái)選中,現(xiàn)在取消選中場(chǎng)景
                    ? index !== cIndex && item !== Empty
                    // 說(shuō)明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]);
                }
            });

            // 通過(guò)限定矩陣的值挑選出滿(mǎn)足條件的商品類(lèi)別
            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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市会前,隨后出現(xiàn)的幾起案子好乐,更是在濱河造成了極大的恐慌,老刑警劉巖瓦宜,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔚万,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡临庇,警方通過(guò)查閱死者的電腦和手機(jī)反璃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)假夺,“玉大人淮蜈,你說(shuō)我怎么就攤上這事∫丫恚” “怎么了礁芦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)悼尾。 經(jīng)常有香客問(wèn)我柿扣,道長(zhǎng),這世上最難降的妖魔是什么闺魏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任未状,我火速辦了婚禮,結(jié)果婚禮上析桥,老公的妹妹穿的比我還像新娘司草。我一直安慰自己,他們只是感情好泡仗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布埋虹。 她就那樣靜靜地躺著,像睡著了一般娩怎。 火紅的嫁衣襯著肌膚如雪搔课。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天截亦,我揣著相機(jī)與錄音爬泥,去河邊找鬼柬讨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛袍啡,可吹牛的內(nèi)容都是我干的踩官。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼境输,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蔗牡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起嗅剖,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤辩越,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后窗悯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體区匣,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偷拔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蒋院,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片莲绰。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欺旧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛤签,到底是詐尸還是另有隱情辞友,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布震肮,位于F島的核電站称龙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏戳晌。R本人自食惡果不足惜鲫尊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沦偎。 院中可真熱鬧疫向,春花似錦、人聲如沸豪嚎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)侈询。三九已至舌涨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扔字,已是汗流浹背泼菌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工谍肤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哗伯。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓荒揣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親焊刹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子系任,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容