在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 } })
以上。