更好的阅度体验
javascript
Immutable的优点java
1. 保证不可变(每次经过Immutable.js操做的对象都会返回一个新的对象) 2. 丰富的API 3. 性能好 (经过字典树对数据结构的共享)
Immutable的问题react
1. 与原生JS交互不友好 (经过Immutable生成的对象在操做上与原生JS不一样,如访问属性,myObj.prop1.prop2.prop3 => myImmutableMap.getIn([‘prop1’, ‘prop2’, ‘prop3’])。另外其余的第三方库可能须要的是一个普通的对象) 2. Immutable的依赖性极强 (一旦在代码中引入使用,很容易传播整个代码库,而且很难在未来的版本中移除) 3. 不能使用解构和对象运算符 (相对来讲,代码的可读性差) 4. 不适合常常修改的简单对象 (Immutable的性能比原生慢,若是对象简单,而且常常修改,不适合用) 5. 难以调试 (能够采用 Immutable.js Object Formatter扩展程序协助) 6. 破坏JS原生对象的引用,形成性能低下 (toJs每次都会返回一个新对象)
原生Js遇到的问题ajax
// 场景一 var obj = {a:1, b:{c:2}}; func(obj); console.log(obj) //输出什么?? // 场景二 var obj = ={a:1}; var obj2 = obj; obj2.a = 2; console.log(obj.a); // 2 console.log(obj2.a); // 2 代码来源:https://juejin.im/post/5948985ea0bb9f006bed7472
// ajax1 this.props.a = { data: 1, } // ajax2 nextProps.a = { data: 1, } //shouldComponentUpdate() shallowEqual(this.props, nextProps) // false // 数据相同可是由于引用不一样而形成没必要要的re-rederning
因为Js中的对象是引用类型的,因此不少时候咱们并不知道咱们的对象在哪里被操做了什么,而在Redux中,由于Reducer是一个纯函数,每次返回的都是一个新的对象(从新生成对象占用时间及内存),再加上咱们使用了connect这个高阶组件,官方文档中虽说react-redux作了一些性能优化,但终究起来,react-redux只是对传入的参数进行了一个浅比较来进行re-redering(为何不能在mapStateToProps中使用toJs的缘由)。再进一步,假如咱们的state中的属性嵌套了好几层(随着业务的发展),对于原来想要的数据追踪等都变得极为困难,更为重要的是,在这种状况下,咱们一些没有必要的组件极可能重复渲染了屡次。
总结起来就是如下几点(问题虽少,但都是比较严重的):redux
1. 没法追踪Js对象 2. 项目复杂时,reducer生成新对象性能低 3. 只作浅比较,有可能会形成re-redering不符合预期(屡次渲染或不更新)
为何不使用深比较性能优化
或许有人会疑惑,为何不使用深比较来解决re-redering的问题,答案很简单,由于消耗很是巨大~ 想象一下,若是你的参数复杂且巨大, 对每个进行比较是多么消耗时间的一件事~
项目复杂后, 追踪困难网络
使用Immutable以后,这个问题天然而然就解决了。所谓的追踪困难,无非就是由于对象是mutable的,咱们没法肯定它到底什么时候何处被改变,而Immutable每次都会保留原来的对象,从新生成一个对象,(与redux的纯函数概念同样)。但也要注意写代码时的习惯:数据结构
// javascript const obj = { a: 1 } function (obj) { obj.b = 2 ... } // Immutable const obj = Map({ a : 1 }) function (obj) { const obj2 = obj.set({ 'b', 2 }) }
reducer生成新对象性能差
当项目变得复杂时,每一次action对于生成的新state都会消耗必定的性能,而Immutable.js在这方面的优化就很好。或许你会疑惑为何生成对象还能优化?请往下看~app
在前面就讲到,Immutable是经过字典树来作==结构共享==的函数
(图片来自网络)
这张图的意思就是
immutable使用先进的tries(字典树)技术实现结构共享来解决性能问题,当咱们对一个Immutable对象进行操做的时候,ImmutableJS会只clone该节点以及它的祖先节点,其余保持不变,这样能够共享相同的部分,大大提升性能。
re-rendering不符合预期
其实解决这个问题是咱们用Immutable的主要目的,先从浅比较提及
浅比较引发的问题在这以前已经讲过,事实上,即便Immutable以后,connect所作的依然是浅比较,但由于Immutable每次生成的对象引用都不一样,哪怕是修改的是很深层的东西,最后比较的结果也是不一样的,因此在这里解决了第一个问题,==re-rendering可能不会出现==。
可是, 咱们还有第二个问题, ==不必的re-rendering==,想要解决这个问题,则须要咱们再封装一个高阶组件,在这以前须要了解下Immutable的 is API
// is() 判断两个immutable对象是否相等 immutable.is(imA, imB);
这个API有什么不一样, ==这个API比较的是值,而不是引用==,So: 只要两个值是同样的,那么结果就是true
const a = Immutable.fromJS({ a: { data: 1, }, b: { newData: { data: 1 } } }) const target1 = a.get('a') const target2 = a.getIn(['b', 'newData']) console.log(Immutable.is(target1, target2)) //is比较的依据就是每一个值的hashcode // 这个hashcode就至关于每一个值的一个ID,不一样的值确定有不一样的ID,相同的ID对应着的就是相同的值。
也就是说,对于下面的这种状况, 咱们能够不用渲染
// ajax1 this.props.a = { data: 1, } // ajax2 nextProps.a = { data: 1, } //shouldComponentUpdate() Immutable.is(this.props, nextProps) // true
最后, 咱们须要封装一个高阶组件来帮助咱们统一处理是否须要re-rendering的状况
//baseComponent.js component的基类方法 import React from 'react'; import {is} from 'immutable'; class BaseComponent extends React.Component { constructor(props, context, updater) { super(props, context, updater); } shouldComponentUpdate(nextProps, nextState) { const thisProps = this.props || {}; const thisState = this.state || {}; nextState = nextState || {}; nextProps = nextProps || {}; if (Object.keys(thisProps).length !== Object.keys(nextProps).length || Object.keys(thisState).length !== Object.keys(nextState).length) { return true; } for (const key in nextProps) { if (!is(thisProps[key], nextProps[key])) { return true; } } for (const key in nextState) { if (!is(thisState[key], nextState[key])) { return true; } } return false; } } export default BaseComponent; 代码来源连接:https://juejin.im/post/5948985ea0bb9f006bed7472
使用Immutable须要注意的点
1. 不要混合普通的JS对象和Immutable对象 (不要把Imuutable对象做为Js对象的属性,或者反过来) 2. 对整颗Reudx的state树做为Immutable对象 3. 除了展现组件之外,其余地方都应该使用Immutable对象 (提升效率,而展现组件是纯组件,不该该使用) 4. 少用toJS方法 (一个是由于否认了Immutable,另外则是操做很是昂贵) 5. 你的Selector应该永远返回Immutable对象 (即mapStateToProps,由于react-redux中是经过浅比较来决定是否re-redering,而使用toJs的话,每次都会返回一个新对象,即引用不一样)
经过高阶组件,将Immutable对象转为普通对象传给展现组件
1. 高阶组件返回一个新的组件,该组件接受Immutable参数,并在内部转为普通的JS对象 2. 转为普通对象后, 新组件返回一个入参为普通对象的展现组件
import React from 'react' import { Iterable } from 'immutable' export const toJS = WrappedComponent => wrappedComponentProps => { const KEY = 0 const VALUE = 1 const propsJS = Object.entries(wrappedComponentProps).reduce( (newProps, wrappedComponentProp) => { newProps[wrappedComponentProp[KEY]] = Iterable.isIterable( wrappedComponentProp[VALUE] ) ? wrappedComponentProp[VALUE].toJS() : wrappedComponentProp[VALUE] return newProps }, {} ) return <WrappedComponent {...propsJS} /> }
import { connect } from 'react-redux' import { toJS } from './to-js' import DumbComponent from './dumb.component' const mapStateToProps = state => { return { // obj is an Immutable object in Smart Component, but it’s converted to a plain // JavaScript object by toJS, and so passed to DumbComponent as a pure JavaScript // object. Because it’s still an Immutable.JS object here in mapStateToProps, though, // there is no issue with errant re-renderings. obj: getImmutableObjectFromStateTree(state) } } export default connect(mapStateToProps)(toJS(DumbComponent))