前兩天介紹了如何更新 state 內的 object,今天則是要來介紹如何更新陣列(Array)。
今天的文章參考官方文件的:
因為是更新 state,所以我們也會需要在更新陣列的時候是 immutable 的,但是一些 JavaScript 內建的 Array
方法都會是有 side effect 的(更改原陣列)像是 push
、pop
的陣列新增跟刪除,或是排列的 sort
之類的,官方文件有幫忙整理一個表格可以直接看:
我們在更新 state 的時候,盡量要少用這些具有 side effect 的方法,除非我們是使用 Immer。
也比較要注意的是 splice 跟 slice 雖然功能差不多,但 splice
是會 mutate 的,而 slice
不會,可能需要特別注意一下。
如果不能使用 push
,要新增一個新項目,會跟 object 一樣,可以使用展開語法,不過 object 的如果使用一樣的 key 會取代舊的,陣列的則是會直接新增一個。
setArtists(
[
...artists, // 複製舊的陣列
{ id: nextId++, name: name } // 新增一個新的項目在陣列最後方
]
);
如果想把最新的放在第一個,就把展開語法放在後面就可以了。
另一個常見的更新會是刪除陣列裡的資料,這時候就可以使用 filter 這個方法,透過條件式留下符合條件的資料。因為 filter
是 immutable 的 function 所以可以放心使用他。
// 刪除 artists 裡 id 跟 artist.id 一樣的
setArtists(
artists.filter(a => a.id !== artist.id)
);
map
重整陣列有時候我們會想把陣列的資料重新整理出一個新陣列,這時候就會用到方法 map。這也是一個非常容易使用到的方法,我自己比較常用的時候是要把資料整理成顯示 UI 好用的格式的時候,就可以使用到。或是想幫陣列內的資料多加個標注,譬如說有一個陣列有學生考試成績,然後我們可以使用 map
,把一定區間的分數歸類成 A-F
的級別之類的。
map
還可以有另一種使用,就是可以獲得每個 item 的 index
,並且透過條件式去更新陣列內容,這樣就可以得到類似 arr[0] = '新資料'
的取代效果(用 map
會把陣列裡的每個一個 item 都跑過一次所以可能效能會不太一樣)。
前面介紹使用展開語法進行陣列的資料新增,但那樣的寫法只能新增最前跟最後,如果是想在任意的陣列位置更新的話,就會需要先使用 slice
獲得前後的兩個子陣列,最後再用展開語法把前後兩個陣列展開,中間再放我們想要新增的資料:
const nextArtists = [
// 新增位置前的子陣列:
...artists.slice(0, insertAt),
// 新增的資料:
{ id: nextId++, name: name },
// 新增位置後的子陣列:
...artists.slice(insertAt)
];
setArtists(nextArtists);
如果真的想使用像是 reverse()
或 sort()
等具有 side effect 的方法,可以先使用展開語法複製一份陣列後再更新:
const nextList = [...list];
nextList.reverse();
setList(nextList);
這樣舊的資料就不會被動到,但是要注意,複製一份陣列的用法,不能去 mutate 陣列裡的 object
const nextList = [...list];
nextList[0].seen = true; // 也會更新到舊的 list[0].seen
setList(nextList);
原因是 nextList[0]
跟 list[0]
會指到同一個 object
所以更新 nextList
的 object 就會更新到 list
的 object。真的要更新就要使用 map
再用 object
的展開語法更新 object
。
今天介紹了各種沒有 side effect 更新陣列的方法,如果真的還是想使用那些 side effect function 的話,也可以直接都使用 Immer 就好,這樣就還是能確保 state 的 immutable。
今天的文章就介紹到這邊,感謝大家耐心地看完,如果有任何建議與問題,都歡迎跟我說,明天見,晚安。