【JavaScript】淺談資料集的新選擇: Set

2023/12/27閱讀時間約 8 分鐘


緣起

在 React 官網的 Choosing the State Structure 中,第四個挑戰題要我們完成一個可以多重選擇的 checkbox:

raw-image


作法是 <input>onChange 事件被觸發後,呼叫名為 onToggle 的 event handler 來比對函式參數 toggledId 是否存在於 selectedIds 這個 state 裡面。

想通之後,我馬上用 includes() 實作,畢竟先前已經把 selectedIds 設為陣列了,而這的確也是官方提供的解答之一

沒錯,除了這個比較直觀的作法之外,React 官方提供了第二個解答,至於差別在哪裡,請先看看這段描述:

One minor downside of using an array is that for each item, you’re calling selectedIds.includes(letter.id) to check whether it’s selected. If the array is very large, this can become a performance problem because array search with includes() takes linear time, and you’re doing this search for each individual item. To fix this, you can hold a Set in state instead, which provides a fast has() operation

由於 includes 會遍歷整個陣列,所以當陣列資料越多,所耗費的時間就越可觀,這樣查找資料等於 O(n)。如果改用 Set 效能更佳

這是筆者第一次看到 Set,於是花了點時間查詢相關資訊,發現似乎是滿實用的東西,因此特別記錄下來~



JavaScript Sets 介紹

Set 是 JavaScript 於 ES2015 所推出的新物件類型,裡面只能存放單一獨特值,也就是無法存在重複的值。如同上面遇到的範例,Set 時常拿出來和陣列一起討論,但要注意兩者差異如下:

  • 陣列是透過 index 對資料進行排序與檢索。
  • Set 是透過 key 來存放資料,所以無法和陣列一樣隨機取用資料 (例如:arr[1]),也無法主動變動順序,也就是說,Set 相對陣列少了許多功能。


新增 Set

目前僅能透過 constructor 來建立新的 Set:

const hashTags = new Set();


在建立新 Set 的時候,可以帶入 iterable,舉凡陣列、物件、字串 (字元的集合):

const hashTags = new Set(["#做自己", "#無濾鏡"])
// result: Set(2) {'#selfie', '#nofilter'}


如果帶入數字的話就沒辦法說,因為數字並非 iterable。替代方案是將數字包進物件或陣列,或是建立空白的 Set 在透過 add() 新增:

new Set(1234)
// Uncaught TypeError: number 1234 is not iterable


新增資料到 Set

如上,透過 add() 來新增資料到 Set 裡面:

const annoyingHashTags = new Set();
annoyingHashTags.add("#做自己");
annoyingHashTags.add("#無濾淨")


前面有提過,Set 的資料都是單一獨特的,所以如果我們嘗試新增重複資料,Set 會自動忽略,就像我學生時期想要追求的對象:

annoyingHashTags.add("#無濾淨") // 新增重複資料
// result: ​Set(2) {'#做自己', '#無濾淨'}


另外請注意,add() 一次只能新增一個項目。


知道 Set 長度

和陣列大同小異,但不是用 length 而是 size

const annoyingHashTags = new Set();
annoyingHashTags.add("#做自己");
annoyingHashTags.add("#無濾淨")
annoyingHashTags.size //2


查詢資料是否存在

終於來到本次的重點了,我們可以透過 has() 來查詢某資料是否存在於 Set 當中,回傳值不意外,當然是 truefalse

const annoyingHashTags = new Set();
annoyingHashTags.add("#做自己");
annoyingHashTags.add("#無濾淨")

annoyingHashTags.has("#做自己"); //true
annoyingHashTags.has("#誰懂我"); //false


移除資料

透過 delete() 來移除指定資料,如果想要一次清空所有資料,則可以使用 clear()

const annoyingHashTags = new Set();
annoyingHashTags.add("#做自己");
annoyingHashTags.add("#無濾淨")

annoyingHashTags.has("#做自己"); //true
annoyingHashTags.delete("#做自己");
annoyingHashTags.has("#做自己"); //false (has been deleted)


迭代資料

前面提過 Set 本質為物件,所以若想要迭代裡面的資料,可以呼喚老朋友 for...of

const annoyingHashTags = new Set();
annoyingHashTags.add("#做自己");
annoyingHashTags.add("#無濾淨")

for(let hashtag of annoyingHashTags) {
console.log("超惱人 hashtag:", hashtag);
}

// 超惱人 hashtag:做自己
// 超惱人 hashtag:無濾淨



結語:

回到最一開始的 React 問題,我們先將 state 換成 Set,然後把 includes() 改成 has()。但由於 React 講求 immutibility,所以不能直接更新物件 (還記得 Set 是物件吧),因此務必記得新增一個 Set,更新完畢後再透過 set function 替換掉舊的 Set 物件:

...

const [selectedIds, setSelectedIds] = useState(
new Set() // 改用 Set
);

const selectedCount = selectedIds.size;

function handleToggle(toggledId) {
// 新增 Set,避免直接更動員來的物件
const nextIds = new Set(selectedIds);
// 資料量一多,has() 的效能將比 includes() 好
if (nextIds.has(toggledId)) {
nextIds.delete(toggledId);
} else {
nextIds.add(toggledId);
}
// 透過 set function 替換掉舊的 Set
setSelectedIds(nextIds);
}

...


完整的題目和說明請參考這邊:

https://react.dev/learn/choosing-the-state-structure


參考資料:







16會員
34內容數
Bonjour à tous,我本身是法文系畢業,這邊會刊登純文組學習網頁開發的筆記。如果能鼓勵更多文組夥伴一起學習,那就太開心了~
留言0
查看全部
發表第一個留言支持創作者!