工作中,React社区推崇搭配一起使用Immutable,就像咖啡牛奶伴侣一样。众所周知React的性能优化我们可以优化组件的嵌套层级,
避免不必要的重绘,以及shouldComponentUpdate来判别组件是否会因为当前属性(props)和状态(state)变化而导致组件输出变化。
一提到React,大家第一时间就想到的虚拟DOM(Virtual DOM)和伴随其带来的高性能(在虚拟dom上进行节点的更改最后在反映到真实dom上)。
但是React提供的是声明式的API(declarative API),好的一方面是让我们编写程序更加方便,但另一方面,却使得我们不太了解内部细节。
一致化处理(Reconciliation)
React采用的是虚拟DOM,每次属性(props)和状态(state)发生变化的时候,render函数返回不同的元素树,
React会检测当前返回的元素树和上次渲染的元素树之前的差异,然后找出何如高效的更新UI。即render就执行diff差异再进行重绘。
shouldComponentUpdate
默认的shouldComponentUpdate会在props和state发生变化时返回true,表示组件会重新渲染,从而调用render函数。
当然了在首次渲染的时候和使用forceUpdate的时候,是不会经过shouldComponentUpdate判断。
合理地编写shouldComponentUpdate函数,从而能避免不必要的一致化处理,使得性能可以极大提高。。我们可以通过
继承React.PureComponent或者通过引入PureRenderMixin模块来达到目的。但是这也存在一个问题:
1 | // 子组件继承PureComponent只会进行浅比较 |
共享的可变状态是万恶之源
JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象。
如 foo={a: 1}; bar=foo; bar.a=2 你会发现此时 foo.a 也被改成了 2。
虽然这样做可以节约内存,但当应用复杂后,这就造成了非常大的隐患,Mutable 带来的优点变得得不偿失。
为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或 deepCopy(深拷贝)来避免被修改,但这样做造成了 CPU 和内存的浪费。
Immutable 可以很好地解决这些问题。
Immutable Data
Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。
Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),
也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,
Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
Map:键值对集合,对应于 Object,ES6 也有专门的 Map 对象
List:有序可重复的列表,对应于 Array
Set:无序且不可重复的列表
比较两个Immutable对象是否相同,只需要使用===就可以轻松判别。因此如果React传入的数据是Immutable Data,那么React就能高效地比较前后属性的变化,从而决定shouldComponentUpdate的返回值。
1 | // 原来的写法 |
Immutable-advantage
Immutable 降低了 Mutable 带来的复杂度
1
2
3
4
5function touchAndLog(touchFn) {
let data = { key: 'value' };
touchFn(data);
console.log(data.key);
}在不了解touchFn函数的代码的情况下,不知道是否对data进行了修改。而如果data为Immutable对象一切都简单了,会打印value。
节省内存 Immutable.js 使用了 Structure Sharing (结构共享)会尽量复用内存,甚至以前使用的对象也可以再次被复用。没有被引用的对象会被垃圾回收。
1 | import { Map } from 'immutable'; |
Undo/Redo,Copy/Paste,时间旅行等功能
并发安全
函数式编程
Immutable-disadvantage
需要熟悉新的api
引入新的库有大小
思维的变化 Immutable 中的 Map 和 List 虽对应原生 Object 和 Array,但操作非常不同,比如你要用 map.get(‘key’)而不是 map.key,array.get(0) 而不是 array[0]。
下面给出一些办法来避免类似问题发生:
使用 Flow 或 TypeScript 这类有静态类型检查的工具 约定变量命名规则:如所有 Immutable 类型对象以 $$ 开头。 使用 Immutable.fromJS 而不是 Immutable.Map 或 Immutable.List 来创建对象,这样可以避免 Immutable 和原生对象间的混用。
另外 Immutable 每次修改都会返回新对象,也很容易忘记赋值。
两个Immutable对象的比较
=== 全等比较内存地址性能最好
Immutable.is() 进行值比较
Immutable.is 比较的是两个对象的 hashCode 或 valueOf(对于 JavaScript 对象)。
由于 immutable 内部使用了 Trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。这样的算法避免了深度遍历比较,性能非常好。
1 | let a = Immutable.Map({a:1}) |
与 Object.freeze、const 区别
Object.freeze 和 ES6 中新加入的 const 都可以达到防止对象被篡改的功能,但它们是 shallowCopy 的。对象层级一深就要特殊处理了。怪不得常量const复杂类型就不行了,直接回答浅拷贝。
react中使用
1 | import { is } from 'immutable'; |
1 | function diff(obj1,obj2){ |
1 | import '_' from 'lodash'; |
常用api
1 | // 声明 |
当然还有现在火热的immer.js,unstated了解一下@_@