react和immutable偶遇的那些事
概要
在项目中经常会遇到一些问题,比如把一个对象赋值给另外一个对象后,如何实现修改被赋值的对象而原对象不变;一般做法是copy,而copy又分为shallowCopy(浅拷贝)和 deepCopy(深拷贝);shallowCopy对于层级比较深的对象来说然并*用,那么只能用deepCopy来避免上面遇到的情况,但deepCopy是一个很耗性能的操作。再比如使用react技术栈来做项目时,经常需要我们手工去判断某些组件是否需要重新渲染来减少我们可爱又傲娇的DOM操作,react提供shouldComponentUpdate钩子函数来判断该组件树是否需要重新渲染,在该钩子函数中一般使用浅比较和深比较来判断该组件树是否需要重新渲染;如果只是浅比较,只需要PureRender就能满足,那么需要深比较呢?上面瞎逼逼一堆废话无非是想说用immutable的好处,它能解决这些问题呀,它的共享结构多牛比呀,零学习成本让你开开心心玩时间旅行。
什么是immutable
immutable听说是Facebook 工程师 Lee Byron 花费 3 年时间打造,与 React 同期出现,但没有被默认放到 React 工具集里(React 提供了简化的 Helper);immutable一旦被创建终身不会变化,每次增加或修改还是删除都是返回一个新的对象;immutable内部采用Structure Sharing(结构共享)来避免每一次deepCopy的性能损耗,每一次增删改都只会改变当前节点和父节点并返回一个新对象。
听说 Immutable 可以给 React 应用带来数十倍的提升,个人感觉10应该达不到,但是确实提升不少效率和避免因对象被修改而导致的错误,虽然可以在项目中使用Object.assign或者lodash来避免原对象被修改而导致的一些错误,但是这两货也有很多缺点,Object.assign只能深拷贝一个层级,而且你会发现满屏的Object.assign,对于处女座的作者肯定是不能容忍此事的发生;Immutable提供了is方法,因为Immutable内部使用的是Trie 数据结构,所以只需要比较hashCode或valueOf来避免深度比较。
下面介绍一下immutable中常用的map和list的一些基础用法,当然还有很多其他类型( Collection、Set、Record、Seq等),本文介绍一些常用API让读者了解使用immutable基本是零学习成本,当然你也可以选择功能更单一的seamless-immutable,代码库非常小,压缩后下载只有 2K:
将js转换成immutable对象:
- immutable.fromJs(obj) //将js对象或数组深拷贝到immutable对象
- immutable.Map(obj)//将js对象深拷贝为immutable对象
- immutable.List(arr) //将js数组深拷贝为immutable数组
immutable.List
- Immutable.List([1,2])//初始化一个list对象
- immutableA.size //求长度
- Immutable.List.isList //判断是否数组
- immutableData.get //取值
- immutableA.set //设值
- ….其他操作
immutable.Map
- Immutable.Map({a: 1})//初始化一个map对象
- Immutable.Map.isMap //判断是否对象
- immutableData.get //取值
- immutableA.set //设值
- ….其他操作
react+redux+immutable开发流程
在react中使用immutable时应该注意避免出现toJS操作,因为toJS和fromJS是把整个数据结构遍历一遍,还要创建新对象来保存值,是很耗性能的操作;建议项目中全部使用immutable,数据从服务请求回来转化成immutable再给redux,只有在提交数据和使用第三方不支持immutable组件或者插件时才转化成原生对象;下面是使用immutable的开发流程。
在没使用immutable之前react中的状态操作
//操作对象之前拷贝 let newState = Object.assign({}, this.state.obj, { 'a': 'test' }); //操作之前数组的拷贝 let newArr = [...arr, ...arr1];
在react中使用immutable后的操作
//使用immutable来初始化状态 this.state = Map(obj); //改变state的值 this.setState(this.state.set('a', 'test'));
当react中使用了redux来进行状态管理时如何在项目中使用immutable,由于 Redux 中内置的 combineReducers和 reducer 中的 initialState 都为原生的 Object 对象,所以不能和 Immutable 原生搭配使用;但是可以通过重写combineReducers或者直接使用redux-immutablejs,本文介绍如何使用redux-immutablejs。
初始化initialState
按照 Redux 的工作流,我们从创建 store 开始。Redux 的 createStore 可以传递多个参数,前两个是: reducers 和 initialState
const initialState = immutable.Map({ list: [], card: { id: '', title: '', desc: '' } });
reducer
在reducer里把以前的Obejct.assagin操作全部去掉,转换成immutable对象。
export default function list(state = initialState, action) { let card; switch (action.type) { case types.EDIT_ENTRY: let lists = state.get('list'); lists = lists.push(action.newData.get('data')); card = action.newData.get('card'); return state.set('list', lists).set('card', card); case types.VALUE_CHANGE: return state.set('card', action.card); case types.ADD: return state.set('card', action.card); default: return state; } }
action
在action里面的所有对象都是immutable,为了区分原生对象,请不要在有的地方使用js原生对象,有的地方使用immutable对象,这样往往会适得其反,容易混淆和immutable和原生转换时性能的消耗。
export function editData() { let card = Map({ id: '', title: '', desc: '' }); return { type: types.ADD, card }; }
接入redux
如果你不传递 initialState,redux-immutable也会帮助你在 store 初始化的时候,通过每个子 reducer 的初始值来构建一个全局 Map 作为全局 state。当然,这要求你的每个子 reducer 的默认初始值是 immutable的。只需要引入redux-immutable就可以在redux里面使用immutable了,
import { combineReducers } from 'redux-immutable'; import list from './list/listRedux'; const rootReducer = combineReducers({ list }); export default rootReducer;
mapStateToProps
function mapStateToProps(state) { return { listMain: state.get('list') }; } export default connect(mapStateToProps)(Main);
总结
immutable不是所有的项目都适合使用,在业务简单数据关联性不复杂等条件下使用反而增加了项目的复杂度,通过上面的开发流程大家也应该看出了immutable侵入性还是杠杠的,所以如果是项目起初考虑上immutable是可以的;但是对于老项目中使用就的考虑一下得失了。