0%

開發紀錄:商品分類實作

最近陸陸續續經手了幾個公司專案的新功能,其中一個功能是要幫所有商品加上分類,然而在實作過程中卡關了一陣子,因此特別撰寫本文來記錄這次任務的幾種解法。

前言

我目前負責公司電商網站的維護,公司專案有個需求是要做出商品分類,在開發時我遇到一個 bug 解不出來,因此在六角學院社團附上 Demo 跟大家請教。

Demo:

卡關背景

邏輯

由於所有商品本身所建的分類較瑣細,而專案所需要呈現的分類涵蓋範圍較廣,因此資料需要經過額外的處理、比對,將所有商品歸納到各大分類之中,如果不屬於所列舉的分類,就把商品放到「其他」類。最後將所有分類與所屬的商品呈現在畫面上。

資料命名

在我附上的 Demo 中,allVariant 陣列是代表所有商品(裡面放的每個物件都是一個商品,type 屬性代表商品本身建的小分類),classifiedProducts 物件則是我想呈現的分類(types 屬性是陣列,放了多個商品本身所建的小分類;arr 屬性也是陣列,用來放過濾後的商品)。

目前作法

用類似雙迴圈的方法,同時迭代 allVariant 陣列與 classifiedProducts 物件,並用 if 判斷如果商品物件的 type 存在於分類物件的 types 陣列中,就把當前的商品物件 push 到當前分類的 arr;否則就 push 到「其他」類的 arr

目前問題

「其他」類中,有許多筆重複的商品資料,且這些資料應該被放到別的分類中,卻都被放到「其他」類。

各種解法

卡在這個問題上已經超過一個早上了,最後真的走投無路於是還是來到六角學院的臉書社團發問,萬幸有不少同學(甚至還有助教)提供解法供我參考,以下就將他們的解法列出來。

解法一:while 迴圈法

提供這個方法的是玟憲同學,他同時還解說為何我的寫法會出錯:

this.allVariantsforEach 後裡面那個 for 迴圈,第一個 varianttype'3d-screen-protector',就會跟 this.classifiedProducts 裡的每個 key 裡的 types 做比對,所以會跑 4 次。在「其他」的那個類別,塑料保護貼1 出現 3 次的原因是:有一個 types 裡有 ['3d-screen-protector'] 所以被 push 進正確的 arr,其餘 3 次都被 push 進 otherProducts.arr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
filter() {
this.allVariants.forEach((variant) => {
const { type } = variant;
const classifiedKey = Object.keys(this.classifiedProducts);

let isMapping = true;
let idx = 0;
while(isMapping) {
if(classifiedKey[idx] === 'otherProducts') {
this.classifiedProducts[classifiedKey[idx]].arr.push(variant);
isMapping = false;
return;
}
if(this.classifiedProducts[classifiedKey[idx]].types.includes(type)) {
this.classifiedProducts[classifiedKey[idx]].arr.push(variant);
isMapping = false;
return;
}
idx += 1;
}
});
}

解法說明:

variant 一個一個去裡面 this.classifiedProducts 找,如果找到,就不要再找。
this.classifiedProducts 有 4 個物件,先把對應的 key 記下來(classifiedKey),跑 while 時,我需要記得我找到哪裡,idx 可以幫我。
所以就先找 classifiedKey[0],如果有的話,要跳出迴圈(isMapping = false);
沒有的話繼續找 classifiedKey[1]
最後找到 classifiedKey[idx] = 'otherProducts',就代表找完了,所以直接加進 arr

解法二:some()

此解法由葉子助教提供~~

我是用全部的資料去跑,用 some() 的方式確認資料的 type 有沒有在分類中,有的話就推到對應的 arr,沒有則推到其他。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
filter() {
const vm = this;
vm.allVariants.forEach(ele => {
let nowKey = '';
const has = Object.keys(vm.classifiedProducts).some(key => {
nowKey = key;
return vm.classifiedProducts[key].types.includes(ele.type);
})
if (has) {
vm.classifiedProducts[nowKey].arr.push(ele);
} else {
vm.classifiedProducts['otherProducts'].arr.push(ele);
}
})
},

我發現這個解法與上一個解法有一些共通點:

  • 都需要用一個變數來記錄「現在找到哪一個 this.classifiedProducts 的 key」
  • 都用額外用一個變數來記錄 type 是否存在於 this.classifiedProducts(布林值)

解法三:for..in

此解法由奕鋒同學提供:

這麼做也可以,只是要確保 otherProducts 是最後一個迭代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
filter() {
this.allVariants.forEach((variant) => {
const { type } = variant;

let hasPushItem = false;
for (let key in this.classifiedProducts) {
if (this.classifiedProducts[key].types.includes(type)) {
this.classifiedProducts[key].arr.push(variant);
hasPushItem = true;
} else if (key === 'otherProducts' && !hasPushItem) {
this.classifiedProducts.otherProducts.arr.push(variant);
hasPushItem = false;
}
}
});
}

解法四:find() + includes()

此解法由強者我同事在 Code Review 時提出,我看了差點膝蓋一軟給他跪下去 😂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
filter() {
this.allVariants.forEach((variant) => {
const { type } = variant;

const matchedProdKey = Object.keys(this.classifiedProducts)
.find(key => this.classifiedProducts[key].types.includes(type))
// find() 會回傳第一個符合條件的元素值,否則回傳 undefined。

const prodKey = matchedProdKey || 'otherProducts';
// 如果 matchedProdKey 是 undefined,prodKey 就會被賦予 'otherProducts' 的值

this.classifiedProducts[prodKey].arr.push(variant);
});
}

雖然最後是採用同事的解法,但其實其他同學提供的解法我也覺得很棒,其中使用了很多 ES6 的用法,我想我之後應該會時不時拿出來複習,因此特別寫這篇文以資紀錄。