前端的immutable設計樣式
有些設計不是為了提升效能而出現的,而是為了提升開發者體驗,更容易開發與降低錯誤率而誕生的,以下就來以經典的immutable來介紹。
mutable與immutable
從字面上的意思可以得知mutable是可變動的,而immutable是不可變動的。
因此操作的行為我們可以用mutable與immutable來表示。
這個設計原則是從OOP開始起來的,尤其是在Java上的object操作最為經典。
如果你是第一次聽到這個名詞,我強烈推薦你花一些時間去研究,React官方也強調這個的重要性。
OOP的使用
以Java為例,可以大致分為:
- Mutable: Date, StringBuilder, StringBuffer…
- Immutable: String class, Wrapper classes, int, float…
Mutable可以在初始化之後,透過methods來改變物件,例如:getter與setter,因為可以透過method來改變,因此是unstable或是不可預期的資料。
Immutable建立實體之後是無法改變的,可以透過getter來取得裡面的資訊,由於資料無法被改變,因此內容是stable且可預期的。
為什麼String在Java是Immutable
在Java,String是一個特別的class,由於String是很常被使用的class,常使用的頻率與Promitive Data Type是差不多的,如果今天一個初學者剛學Java,就要學習Object reference的概念,相信這是一個很容易混淆且不易被接受的問題。因此基於某些理由的前提下,String在Java是屬於Immutable的一種。(相信這也解答了以前學Java造成混淆的一些疑惑了)
為什麼要Immutable
簡單幾個字來詮釋:單純,可預期
我們在開發程式的時候,很常因為對相同的資料進行操作而可能造成非預期的行為。
例如開發這個頁面會用到userData:
1 | const userData = { |
然後今天輪到另一位接手開發程式,他做了這樣的事情:
1 | const userData = { |
之後換到其他人開發的時候發現,奇怪?我畫面的呈現怎麼怪怪的,後端的資料明明是正常的呀?
仔細一看發現:
啊!Magic number!
啊!unpure function!
啊!mutable!!!
這訊息量實在太大了,不好承受呀!
事實上這不是一個很好的操作,因為在中間更改資料這個動作,其實很難在第一時間debug出問題來,特別是這個magic number搭配mutable的操作是很大的傷害。
要改善上面的專案,可以參考以下的方法:
1 | const userData = { |
掌握每次更新資料時,都是產生新的物件。
你可能會問:為什麼每次都要產生新的資料?這樣不是很耗資源?空間複雜度是不是更高?
不,這是一個很好的設計,用不到的丟掉,用到的留下,程式語言背後就會幫你處理好背後的事情了。
比起花時間在效能議題上,開發者更需要專注在程式邏輯上的開發,因此可以更容易提升開發體驗快速產出。
Immutable應用在前端上
Immutable的原則很單純,資料被建立的時候即不可改變,那麼我們如何用在JavaScript上呢?
迷:我知道我知道,const也是可以做到的嘛!
對…也不對,只講對了一半,const在面對Primitive Data Type時是很有用的,但是面對Object Data Type時,一切都變了樣。
Object的Mutable例子
- Object的操作:
1 | const userData = { |
- Object的Array的操作:
1 | const array = []; |
所以const並無法做到完整的Immutable
Functional Programming
如果是build-in的FP methods呢?
有些是Immutable,有些是mutable
Immutable的例子有:map, reduce, slice…,這些都會產生新的物件。
但是像是splice就是屬於mutable了,會修改原本的物件。
可以參考這篇文章整理的參考表格:
就JS的角度來看,好像有點亂?要注意的地方似乎挺多的,那該如何做呢?
可以參考以下的方法:
- 制定專案設計原則
- 使用Immutablejs套件
下面來簡單介紹一下可以採用的方式
制定專案設計原則
團隊只要有設計上的共識,基本上就可以歸納出什麼狀況需要做什麼操作。
可以多參考官方文件或是其他套件的撰寫風格引入進來。
也可以參考eslint的定義,來嚴格規定團隊的設計風格:eslint-plugin-immutable
不可接受的方式:
1 | let users = [ { name: '' } ]; |
可以接受的方式:
1 | let users = [ { name: '' } ]; |
Immutablejs套件
Immutablejs提供了常用的方法,並把他們統一為Immutable的寫法。
讓我們來看一下官方的範例:
1 | var map1 = Immutable.Map({ a: 1, b: 2, c: 3 }); |
有發現不一樣的地方嗎?他用map2去接set好的map1。
map1並不會改變物件內的東西,則是產生新的map2。