本文幫助你們從0瞭解什麼是immutable
,以及它有什麼做用,是在什麼背景下應運而生的。前端
最重要的是它是咱們平常開發中經常使用的技術之一,與React配合使用的immutable
能給咱們開發者帶來多大的便利,本文會依次講到。算法
在咱們的平常開發中,我相信你們都遇到過一種困擾,那就是如何將後端返回的數據,深拷貝一份,再供咱們本身使用呢?後端
若是用過React
框架開發過項目的小夥伴,也必定記得Redux
中的reducer
是基於純函數設計的,要求返回的狀態數據(對象或數組),須要先深拷貝一份(目的是:防止影響老狀態),再根據本身的開發需求對其拷貝後的值操做?數組
來看下面這樣一個例子:性能優化
let list = [
{ name: 'house', age: 18}
]
let newList = [...list];
newList[0].name = "xiaoming";
console.log(list[0].name); // "xiaoming"
複製代碼
顯然上面例子中的原數組list
,被咱們不輕易間串改了,其實緣由很簡單,就是由於ES6中的展開運算符[...list]
是一個淺拷貝,淺拷貝的意思就是隻複製對象或數組的第一級內容。markdown
在上面中,能夠發現通過展開運算符的淺拷貝,只複製了其內層引用類型的地址,當經過索引找到其引用地址,並改變它的時候,改的就是list
原數組自己。數據結構
固然,有的小夥伴能夠想到:當訪問到對象的屬性值的時候,將屬性值再進行遞歸對比,這樣就達到了深層對比的效果,可是想一想一種極端的狀況,就是在屬性有一萬條的時候,只有最後一個屬性發生了變化,那咱們就不得已將一萬條屬性都遍歷。這是很是浪費性能的。框架
回到問題的本質,不管是直接用淺層比對,仍是進行深層比對,咱們最終想知道的就是原對象裏的屬性有無改變。函數
在這樣的條件下,immutable 數據應運而生。性能
immutable數據一種利用結構共享
造成的持久化數據結構
,一旦有部分被修改,那麼將會返回一個全新的對象,而且原來相同的節點會直接共享。
每次修改一個 immutable 對象時都會建立一個新的不可變的對象,在新對象上操做並 不會影響到原對象的數據。
具體點來講,immutable 對象數據內部採用是多叉樹的結構,凡有節點被改變,那麼它和與它相關的全部上級節點
都更新
。
用一張動圖來模擬一下這個過程:
是吧!只更新了父節點
,比直接比對全部的屬性簡直強太多,而且更新後返回了一個全新的引用
,即便是淺比對也能感知到數據的改變。
所以,採用 immutable 既可以最大效率地更新數據結構,又可以和現有的 React中的 PureComponent (memo) 順利對接,感知到狀態的變化,是提升 React 渲染性能的極佳方案
。
不過,immutable 也有一些被開發者吐槽的點,首先是 immutable 對象
和 JS 對象
要注意轉換,不能混用,這個你們注意適當的時候調用 toJS
或者 fromJS
便可,問題並不大。
immutable
實現的原理是:持久化數據結構
,也就是使用舊數據建立新數據時,要保證舊數據同時可用且不變。同時爲了避免deepCopy
把全部節點都複製一遍帶來的性能損耗
。
immutable
使用了結構共享
,即若是對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。
與React中的 PureComponent(memo) 相結合,咱們知道PureComponent
可以在內部幫咱們比較新props
跟舊props
,新state
和舊state
,若是值相等
或者對象含有的相同的屬性、且屬性值相等
,便肯定shouldComponentUpdate
返回true或者false,從而判斷是否再次渲染render
函數。
看上述代碼,咱們能夠看出來,當代碼中使用immutable
第三庫的時候,能夠精確地深拷貝
a 對象,改a對象
中的select
屬性賦值給b
以後,並不會影響原對象a
,而b
的select
屬性變爲了新值。
若是上述select
屬性給一個組件用,由於其值被改變了,致使shouldComponentUpdate
應該返回true,而filter
屬性給另外一個組件用,經過判斷,並沒有變化,致使shouldComponentUpdate
應該返回false,故此組件就避免了重複的diff算法對比
,大大提升了React中的性能優化。
這麼好用的第三方庫,咱們來看一下它的基本用法:
(1)Map() 包裹對象
const { Map } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
console.log(map1.get('b')); // 2
console.log(map2.get('b')); // 50
複製代碼
(2)List() 包裹數組
const { List } = require('immutable');
const list1 = List([ 1, 2 ]);
const list2 = list1.push(3, 4, 5); // [1,2,3,4,5]
const list3 = list2.unshift(0); // [0,1,2,3,4,5]
const list4 = list1.concat(list2, list3); // [1,2,3,4,5,0,1,2,3,4,5]
//push, set, unshift or splice 均可以直接用,返回一個新的immutable對象
複製代碼
(3)merge() 鏈接對象 | concat() 鏈接數組
const { Map, List } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3, d: 4 });
const map2 = Map({ c: 10, a: 20, t: 30 });
const obj = { d: 100, o: 200, g: 300 };
const map3 = map1.merge(map2, obj);
// Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }
const list1 = List([ 1, 2, 3 ]);
const list2 = List([ 4, 5, 6 ]);
const list3 = list1.concat(list2, array);
// List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
複製代碼
(4)toJS() 把immutable對象轉換爲js對象
const { Map, List } = require('immutable');
const deep = Map({ a: 1, b: 2, c: List([ 3, 4, 5 ]) });
console.log(deep.toObject()); // { a: 1, b: 2, c: List [ 3, 4, 5 ] }
console.log(deep.toArray()); // [ 1, 2, List [ 3, 4, 5 ] ]
console.log(deep.toJS()); // { a: 1, b: 2, c: [ 3, 4, 5 ] }
JSON.stringify(deep); // '{"a":1,"b":2,"c":[3,4,5]}'
複製代碼
(5)fromJS() 包裹 js對象轉換爲immutable對象
const { fromJS } = require('immutable');
const nested = fromJS({ a: { b: { c: [ 3, 4, 5 ] } } });
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } });
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
console.log(nested2.getIn([ 'a', 'b', 'd' ])); // 6
//若是取一級屬性 直接經過get方法,若是取多級屬性 getIn(["a","b","c"]])
// setIn 設置新的值
const nested3 = nested2.setIn([ 'a', 'b', 'd' ], "kerwin");
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: "kerwin" } } }
// updateIn 回調函數更新
const nested3 = nested2.updateIn([ 'a', 'b', 'd' ], value => value + 1);
console.log(nested3);
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }
const nested4 = nested3.updateIn([ 'a', 'b', 'c' ], list => list.push(6));
// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
複製代碼
相對較全的immutable
一些經常使用方法,都在這裏給你們總結了,你們在項目中常常用就能夠熟練掌握了。
一句話總結:immutable適用於任何
框架開發,只要是能引入第三方庫的框架,均可以使用。
下面簡單列舉一下,我在React開發中reducer
的簡單使用狀況:
狀況1:未使用immutable
時
下面的代碼的狀況十分危險,不建議這樣用,由於一旦當newStateList
中的類型較爲複雜(包含引用類型),且須要修改newStateList
時,就會發生報錯,由於[...xxx, ...xxx]
是淺拷貝,會影響原來的狀態。
狀況2:使用immutable
時
經過store中傳遞過來的老狀態prevState
先轉化爲immutable
對象,對深拷貝以後的對象,再進行修改等操做時,不會影響原狀態,最後再經過toJS()
轉換爲js對象便可
immutable並無深層比較,由於深層比較的開銷是很大的。
immutable數據調用set方法修改的時候僅僅
修改本節點
和它上面全部的相關上層節點
,保證了不同的引用
,也更新了數據。更重要的是,它避免了無關數據的比對,提升了性能。
若是你以爲這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
『在看』
,纔是我創造的動力。『前端時光屋』
,不按期分享原創知識。