在redux中使用Immutable
1、什么是Immutable?
Immutable是一旦創建,就不能被更改的數據。
對Immutable對象的任何修改或添加刪除操作都會返回一個新的Immutable對象。
Immutable實現的原理是:Persistent Data Structure(持久化數據結構),
也就是數據改變時(增刪改)要保證舊數據同時可用且不變
為了避免深拷貝把所有節點都復制一遍帶來的性能損耗,Immutable使用了Structural Sharing(結構共享)
即如果對象樹節點發生變化,只修改這個結點和受它影響的父節點,其他節點共享
2、immutable常用API
//Map() 原生object轉Map對象 (只會轉換第一層,注意和fromJS區別) immutable.Map({name:'danny', age:18}) //List() 原生array轉List對象 (只會轉換第一層,注意和fromJS區別) immutable.List([1,2,3,4,5]) //fromJS() 原生js轉immutable對象 (深度轉換,會將內部嵌套的對象和數組全部轉成immutable) immutable.fromJS([1,2,3,4,5]) //將原生array --> List immutable.fromJS({name:'danny', age:18}) //將原生object --> Map //toJS() immutable對象轉原生js (深度轉換,會將內部嵌套的Map和List全部轉換成原生js) immutableData.toJS(); //查看List或者map大小 immutableData.size 或者 immutableData.count() // is() 判斷兩個immutable對象是否相等 immutable.is(imA, imB); //merge() 對象合並 var imA = immutable.fromJS({a:1,b:2}); var imA = immutable.fromJS({c:3}); var imC = imA.merge(imB); console.log(imC.toJS()) //{a:1,b:2,c:3} //增刪改查(所有操作都會返回新的值,不會修改原來值) var immutableData = immutable.fromJS({ a:1, b:2, c:{ d:3 } }); var data1 = immutableData.get('a') // data1 = 1 var data2 = immutableData.getIn(['c', 'd']) // data2 = 3 getIn用於深層結構訪問 var data3 = immutableData.set('a' , 2); // data3中的 a = 2 var data4 = immutableData.setIn(['c', 'd'], 4); //data4中的 d = 4 var data5 = immutableData.update('a',function(x){return x+4}) //data5中的 a = 5 var data6 = immutableData.updateIn(['c', 'd'],function(x){return x+4}) //data6中的 d = 7 var data7 = immutableData.delete('a') //data7中的 a 不存在 var data8 = immutableData.deleteIn(['c', 'd']) //data8中的 d 不存在
3、immutable可以讓代碼更簡潔、提高性能、讓redux更快更方便更安全
在redux中每個reducer都返回一個新的對象(數組),常常會看到這樣的代碼:
// reducer ... return [ ...oldArr.slice(0,3), newValue, ...oldArr.slice(4) ];
為了返回新的對象(數組),不得不有上面奇怪的樣子,
而在使用更深的數據結構時會變的更棘手。
讓我們看看Immutable的做法:
// reducer ... return oldArr.set(4, newValue);
Immutable使用了Structure Sharing會盡量復用內存,
甚至以前使用的對象也可以再次被復用,
未引用的對象會被垃圾回收。
4、immutable使用過程中的一些注意點
a、fromJS和toJS會深度轉換數據,隨之帶來的開銷較大,盡可能避免使用,單層數據轉換使用Map()和List() b、js是弱類型,但Map類型的key必須是string!(也就是我們取值是要用get('1')而不是get(1)) c、所有針對immutable變量的增刪改必須左邊有賦值,因為所有操作都不會改變原來的值,只是生成一個新的變量 d、獲取深層深套對象的值時不需要做每一層級的判空(JS中如果不判空會報錯,immutable中只會給undefined) e、immutable對象直接可以轉JSON.stringify(),不需要顯式手動調用toJS()轉原生 f、判斷對象是否是空可以直接用size g、調試過程中要看一個immutable變量中真實的值,可以chrome中加斷點,在console中使用.toJS()方法來查看
5、在react中使用immutable
react做性能優化時,可以使用shouldComponentUpdate(),
因為無論子組件用沒用到父組件的參數只要父組件重新渲染了,
子組件就會重新渲染
而shouldComponentUpdate()很好地幫我們解決了這個問題,
//在render函數調用前判斷:如果前后state中Number不變,通過return false阻止render調用 shouldComponentUpdate(nextProps,nextState){ if(nextState.Number == this.state.Number){ return false } }
通過這個鈎子我們可以很巧妙地避免了很多組件的重新渲染這種浪費性能的行為。
但是這個鈎子默認返回true,也就是說它默認是都重新渲染的,
那么就需要多次使用,
而我們在使用原生屬性的時候,為了得出是true還是false
不得不使用deepCopy、deepCompare,
而這兩種方法非常消耗性能
而在有了Immutable之后,
Immutable 則提供了簡潔高效的判斷數據是否變化的方法,
來減少 React 重復渲染,提高性能,只需 ===
和 is
比較就能知道是否需要執行 render()
,
而這個操作幾乎 0 成本,所以可以極大提高性能。
修改后的 shouldComponentUpdate
是這樣的:
import { is } from 'immutable'; shouldComponentUpdate: (nextProps = {}, nextState = {}) => { const thisProps = this.props || {}, thisState = this.state || {}; //判斷長度是否改變,長度改變的話,數據一定改一定需要重新渲染 if (Object.keys(thisProps).length !== Object.keys(nextProps).length || Object.keys(thisState).length !== Object.keys(nextState).length) { return true; } //當原數據和next的數據長度一致時需要遍歷循環比較 for (const key in nextProps) { if (!is(thisProps[key], nextProps[key])) { return true; } } for (const key in nextState) { if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) { return true; } } return false; }
6、redux中應用immutable的小demo
a、我的demo大概是這樣
b、store文件夾下的index.js中引入immutable
import { createStore, applyMiddleware } from 'redux' //引入immutable import Immutable from 'immutable' import thunk from 'redux-thunk' import reducers from './reducers' //變為map函數 const initialState = Immutable.Map(); //注入 const store = createStore(reducers, initialState, applyMiddleware(thunk)) export default store
c、store文件夾下的reducers.js中修改聯合reducer的方法
//以前引入的是redux的combineReducers方法 // import { combineReducers } from 'redux' //現在改為引入redux-immutable中 import { combineReducers } from 'redux-immutable' import { reducer as cookbook } from 'pages/cookbook' import { reducer as menu } from 'pages/menu' export default combineReducers({ cookbook, menu })
d、在actionType.js和actionCreator.js中無變化,仍是定義變量和獲取數據
e、在頁面的reducer.js中
import { CHANGE_FROM } from './actionTypes' //引入fromJS import { fromJS } from 'immutable' //把獲取到的數據變成immutable的Map()形式 const defaultState = fromJS({ from: 'category' }) //對state進行改變的時候采用immutable的方法 export default (state=defaultState, action) => { if (action.type === CHANGE_FROM) { return state.set('from', action.from) } return state }
f、在頁面使用的時候轉化成原生使用
//通過toJS()方法轉換為原生再進行map遍歷 let data = this.props.categories && this.props.categories.get('熱門').toJS().slice(0, 11).map((value, index) => { return { icon: value.img, text: value.title } })
以上。