有機會的話想導入看看。
簡述
Immutable(不可變)在開發中是一個很重要的概念,它有幾個優點:
在 JavaScript 裡「Array」和「Object」都是 mutable(可變)的資料型態,這時候如果沒有用「Immutable 的觀念」來操作的話就會引發一些不可預測的行為。
舉例來說,假設我有一個 Array,我想取出最後一筆的值,這時候就會出現「原本的 Array 也被改掉了」的情況發生,像這樣:
1 2 3 4
| const array = [1, 2, 3, 4, 5] const lastOne = array.pop() console.log(lastOne) console.log(array)
|
我確實拿到了最後一個值 5
,但注意原本的 array 也被改掉了。
為了不改變原本的 array
,我們可以用「拷貝」的方式來做,像這樣:
1 2 3 4 5
| const array = [1, 2, 3, 4, 5] const lastOne = [...array].pop()
console.log(lastOne) console.log(array)
|
這邊是用「淺拷貝」來做複製,當資料型態更複雜時你可能會需要用「深拷貝」來處理。只是順便提一下,不是這篇要提的重點。
總之這種作法就是「Immutable」的觀念,我每一次都是「產生一個全新的物件」,而不是直接去改「原本的 Reference」,所以原本的 array
永遠不會被動到。
不過這就引發了另外一個問題:
如果我只是想改某個地方,卻又要先把原始資料整筆複製一份的話,豈不是有點浪效能?
所以這篇就來介紹 Immutable.js,一個由臉書開源出來的套件。
List
建立方式
這個基本上就是 JS 中的 Array,建立方式也很簡單:
1 2 3
| const list = List([1, 2, 3, 4, 5]) console.log(list) console.log(list.toJS())
|
toArray
其中一個轉成 JS 的方法,但要注意是「淺拷貝」:
1 2 3 4 5 6 7 8 9
| const list = List([[1], 2, 3, 4, 5]) const toArray1 = list.toArray() const toArray2 = list.toArray()
console.log(toArray1) console.log(toArray2) console.log(toArray1 === toArray2) console.log(toArray1[0] === toArray2[0])
|
toJS
轉成 JS 的方法,這個才是「深拷貝」:
1 2 3 4 5 6 7 8 9
| const list = List([[1], 2, 3, 4, 5]) const toArray1 = list.toJS() const toArray2 = list.toJS()
console.log(toArray1) console.log(toArray2) console.log(toArray1 === toArray2) console.log(toArray1[0] === toArray2[0])
|
isList
檢查值是不是 List 這個型態:
1 2 3 4 5
| const array = [1, 2, 3, 4, 5] const list = List([[1], 2, 3, 4, 5])
console.log(List.isList(array)) console.log(List.isList(list))
|
of
如果要透過 List 來建立新的 List 的話,要注意值只能是 Array,不可以是別的值:
1 2 3 4
| const list1 = List([1, 2, 3])
const list2 = List(1, 2, 3)
|
如果要傳非 Array 的值的話要改用 of
來建立:
1 2 3 4 5 6 7
| const list1 = List.of(1, 2, 3) const list2 = List.of({ name: 'PeaNu', age: 20 }) const list3 = List.of(false, true)
console.log(list1.toArray()) console.log(list2.toArray()) console.log(list3.toArray())
|
size
用來查看長度(length)的屬性:
1 2
| const list = List([1, 2, 3]) console.log(list.size)
|
set
用來設定陣列值的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const list = List([1, 2, 3])
const list2 = list.set(3, 4) console.log(list2.toArray())
const list3 = list.set(0, 100) console.log(list3.toArray())
const list4 = list.set(5, 10) console.log(list4.toArray())
const list5 = list.set(-1, 0) console.log(list5.toArray())
|
delete
刪除陣列值,可以想成是 Imuutable 的 splice(0, index)
:
1 2 3 4 5 6 7 8 9
| const list = List([1, 2, 3, 4, 5]) const list1 = list.delete(0) console.log(list1.toArray())
const list2 = list.delete(3) console.log(list2.toArray())
const list3 = list.delete(-1) console.log(list3.toArray())
|
insert
在指定的 index 中插值:
1 2 3 4 5 6
| const list = List([1, 2, 3, 4, 5]) const list2 = list.insert(0, 0) console.log(list2.toArray())
const list3 = list.insert(2, 'Hi') console.log(list3.toArray())
|
push & pop
熟悉的 push 與 pop,不過是 Immutable 版:
1 2 3 4 5
| const list = List([1, 2, 3, 4, 5]) const list2 = list.push(6)
console.log(list.toArray()) console.log(list2.toArray())
|
1 2 3 4 5
| const list = List([1, 2, 3, 4, 5]) const list2 = list.pop()
console.log(list.toArray()) console.log(list2.toArray())
|
shift & unshift
熟悉的 shift 與 unshift,不過是 Immutable 版:
1 2 3 4 5 6 7
| const list = List([1, 2, 3, 4, 5]) const list2 = list.unshift(0) const list3 = list.shift()
console.log(list.toArray()) console.log(list2.toArray()) console.log(list3.toArray())
|
merge
把兩個陣列合併:
1 2 3 4
| const a = List([1, 2, 3, 4, 5]) const b = List([6, 7, 8, 9])
console.log(a.merge(b).toArray())
|
sortBy
想快速用「字典序」來排序的時候還不錯用:
1 2 3
| const foods = List([{ name: 'Orange' }, { name: 'Grape' }, { name: 'Banana' }, { name: 'Candy' }]) const sortedFoods = foods.sortBy((food) => food.name) console.log(sortedFoods.toArray())
|
輸出結果:
1
| ;[{ name: 'Banana' }, { name: 'Candy' }, { name: 'Grape' }, { name: 'Orange' }]
|
Map
簡單來說就是物件,基本上都跟 List 的用法差不多,所以這裡就直接示範了。
建立方式
直接把 Object 傳進去就行了:
1 2
| const map = Map({ name: 'PeaNu', age: 20, gender: 'man' }) console.log(map.toObject())
|
set
設定內容(單層):
1 2
| const map = Map().set('name', 'PeaNu').set('age', 20).set('gender', 'man') console.log(map.toObject())
|
setIn
設定內容(多層):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const a = Map({ name: 'PeaNu', age: 20, gender: 'man', relation: { wife: 'PPB', father: 'JOJO', friend: 'DIO' } }) const newOne = a.setIn(['relation', 'father'], 'Caesar')
console.log(newOne.toObject())
|
delete
刪除某個屬性(單個):
1 2 3 4 5 6 7 8
| const map = Map({ name: 'PeaNu', age: 20, gender: 'man' }) const map2 = map.delete('name') console.log(map.toObject()) console.log(map2.toObject())
|
deleteAll
刪除多個欄位(傳陣列):
1 2 3 4 5 6 7
| const map = Map({ name: 'PeaNu', age: 20, gender: 'man' }) const map2 = map.deleteAll(['name', 'age']) console.log(map2.toObject())
|
merge
合併:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const a = Map({ name: 'PeaNu', age: 20, gender: 'man' })
const b = Map({ brother: 'JOJO', father: 'DIO', wife: 'PPB' })
console.log(a.merge(b).toObject())
|
mergeWith
一般的 merge
如果碰到「相同值」的話會直接覆寫,如果你希望「不要覆蓋」,想做一些處理的話可以改用這個方式:
1 2 3 4 5 6 7 8 9 10 11 12
| const a = Map({ a: 10, b: 20, c: 30 }) const b = Map({ a: 40, b: 50, e: 60, f: 30 })
const result1 = a.mergeWith((oldVal, newVal) => { return oldVal + newVal }, b)
const result2 = a.merge(b)
console.log(result1.toObject()) console.log(result2.toObject())
|
map
給 Object 用的 map,我覺得很酷:
1 2 3
| const obj = Map({ a: 10, b: 20 }) const obj2 = obj.map((num) => num * 2) console.log(obj2.toObject())
|
reverse
可以反轉內容:
1 2 3
| const obj = Map({ a: 10, b: 20 }) const obj2 = obj.reverse() console.log(obj2.toObject())
|
sort
神奇的排序:
1 2 3 4 5 6 7
| const obj = Map({ a: 5, b: 12, c: 10, d: 8, e: 24, f: 3 })
const asc = obj.sort((a, b) => b - a) const desc = obj.sort((a, b) => a - b)
console.log(asc.toObject()) console.log(desc.toObject())
|