Open jtwang7 opened 3 years ago
参考文章: Immutable 详解及 React 中实践 React性能优化的中流砥柱——Immutable数据流
JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象。JS 对象的引用赋值特点虽然可以大大节约栈内存空间,但当应用复杂后,这就造成了非常大的隐患,Mutable 带来的优点变得得不偿失。为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或 deepCopy(深拷贝)来避免被修改,但这样做造成了 CPU 和内存的浪费。Immutable 可以很好地解决这些问题。
immutable 数据一种利用结构共享形成的持久化数据结构,一旦有部分被修改,那么将会返回一个全新的对象,并且原来相同的节点会直接共享。
Persistent Data Structure(持久化数据结构):使用旧数据创建新数据时,要保证旧数据同时可用且不变,返回全新的数据。 Structural Sharing(结构共享):如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享,避免了 deepCopy 把所有节点都复制一遍带来的性能损耗。
每次修改一个 immutable 对象时都会创建一个新的不可变的对象,在新对象上操作并不会影响到原对象的数据。 immutable 对象数据内部采用是多叉树的结构,如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。如图:
function touchAndLog(touchFn) { let data = { key: 'value' }; touchFn(data); console.log(data.key); // 猜猜会打印什么? }
在不查看 touchFn 的代码的情况下,因为不确定它对 data 做了什么,你是不可能知道会打印什么。但如果 data 是 Immutable 的呢,你可以很肯定的知道打印的是 value。
Immutable 能够完整记录历史的状态,因此我们不用担心 Immutable 数据会在某些地方被改变。它就像一个“快照”,你只要保留它,就能在之后的任何地方访问到历史的记录。
import { Map} from 'immutable'; let a = Map({ select: 'users', filter: Map({ name: 'Cam' }) }) let b = a.set('select', 'people');
a === b; // false a.get('filter') === b.get('filter'); // true
上述代码中,`filter` 属性没有改变,因此复用了该节点下的对象。 3. 拥抱函数式编程 Immutable 本身就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。因为只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。 ### 缺点 1. 需要学习新的 API 2. 增加了资源文件大小 3. **容易与原生对象混淆** 这点是我们使用 Immutable.js 过程中遇到最大的问题。写代码要做思维上的转变。 虽然 Immutable.js 尽量尝试把 API 设计的原生对象类似,有的时候还是很难区别到底是 Immutable 对象还是原生对象,容易混淆操作。 Immutable 中的 Map 和 List 虽对应原生 Object 和 Array,但操作非常不同,比如你要用 `map.get('key')` 而不是 `map.key`,`array.get(0)` 而不是 `array[0]`。另外 Immutable 每次修改都会返回新对象,也很容易忘记赋值。 > 庆幸的是,ES6 之后,JS 对于对象和数组的使用,渐渐从命令式编程逐渐转向函数式编程,并且新增了 Map 和 Set 等新的数据结构。 如何避免“混淆”? 1. 使用 Flow 或 TypeScript 这类有静态类型检查的工具 2. 约定变量命名规则:如所有 Immutable 类型对象以 $$ 开头。 3. 使用 `Immutable.fromJS` 而不是 `Immutable.Map` 或 `Immutable.List` 来创建对象,这样可以避免 Immutable 和原生对象间的混用。 > 使用前通过 `Immutable.fromJS()` 将 JS 对象转为 Immutable 数据,返回数据时再通过 `Immutable.toJS()` 将数据转回 JS 对象。 ## immutable 文档 参考 [Immutable-js](https://immutable-js.com/docs/v4.0.0-rc.13) 官方文档。 常用的方法如下: 1. Map() ```js const { Map } = require('immutable'); // 将对象转为 Immutable Map 数据结构 const map1 = Map({ a: 1, b: 2, c: 3 }); // immutable-map 的常用方法, 类似于 js Map 结构: set, get, update ... const map2 = map1.set('b', 50); console.log(map1.get('b')); // 2 console.log(map2.get('b')); // 50
const { List } = require('immutable');
// 将对象转为 Immutable List 数据结构'; const list1 = List([ 1, 2 ]);
// Immutable List 的方法,类似于 js array const list2 = list1.push(3, 4, 5); // [1,2,3,4,5] const list3 = list2.unshift(0); // [0,1,2,3,4,5] const list4 = list1.concat(list2, list3); // [1,2,3,4,5,0,1,2,3,4,5] //push, set, unshift or splice 都可以直接用,返回一个新的immutable对象
3. merge() 连接对象 | concat() 连接数组 ```js // Immutable 不能使用扩展运算符,因此需要使用其规定的方法来实现拼接 const { Map, List } = require('immutable'); const map1 = Map({ a: 1, b: 2, c: 3, d: 4 }); const map2 = Map({ c: 10, a: 20, t: 30 }); const obj = { d: 100, o: 200, g: 300 }; // js object 也可以作为参数,其会先被转为 Immutable 再拼接 const map3 = map1.merge(map2, obj);// Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 } const list1 = List([ 1, 2, 3 ]); const list2 = List([ 4, 5, 6 ]); const array = [7, 8, 9]; const list3 = list1.concat(list2, array);// List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
// fromJS 是 immutable 库的一个方法 const { fromJS } = require('immutable'); const nested = fromJS({ a: { b: { c: [ 3, 4, 5 ] } } });// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } });// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
//如果取一级属性 直接通过get方法,如果取多级属性 getIn(["a","b","c"]]) console.log(nested2.getIn([ 'a', 'b', 'd' ])); // 6
// setIn 设置新的值const nested3 = nested2.setIn([ 'a', 'b', 'd' ], "kerwin");// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: "kerwin" } } }
// updateIn 回调函数更新 const nested3 = nested2.updateIn([ 'a', 'b', 'd' ], value => value + 1); console.log(nested3);// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } } const nested4 = nested3.updateIn([ 'a', 'b', 'c' ], list => list.push(6));// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
5. toJS() 把immutable对象转换为js对象 ```js const { Map, List } = require('immutable'); const deep = Map({ a: 1, b: 2, c: List([ 3, 4, 5 ]) }); // 转为 object console.log(deep.toObject()); // { a: 1, b: 2, c: List [ 3, 4, 5 ] } // 转为 array console.log(deep.toArray()); // [ 1, 2, List [ 3, 4, 5 ] ] // 深层转换 console.log(deep.toJS()); // { a: 1, b: 2, c: [ 3, 4, 5 ] } JSON.stringify(deep); // '{"a":1,"b":2,"c":[3,4,5]}'
未使用immutable时,一旦当newStateList中的类型较为复杂(包含引用类型),且需要修改newStateList时,就会发生报错,因为[...xxx, ...xxx]是浅拷贝,会影响原来的状态。 Redux 结合 Immutable 使用,通过store中传递过来的老状态prevState先转化为immutable对象,对深拷贝之后的对象,再进行修改等操作时,不会影响原状态,最后再通过toJS()转换为js对象即可。
[...xxx, ...xxx]
import {fromJS} from 'immutable'; // 先将接受数据转为 Immutable 格式 export const reducer = (prevState = fromJS([]), action) { let {type, payload} = action; switch (type) { case 'AddList': let newStateList = prevState.concat(payload); // 需用 Immutable 内的数据操作方式操纵数据 return newStateList.toJS(); // 返回 JS 数据格式 } }
参考文章: Immutable 详解及 React 中实践 React性能优化的中流砥柱——Immutable数据流
前言
JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象。JS 对象的引用赋值特点虽然可以大大节约栈内存空间,但当应用复杂后,这就造成了非常大的隐患,Mutable 带来的优点变得得不偿失。为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或 deepCopy(深拷贝)来避免被修改,但这样做造成了 CPU 和内存的浪费。Immutable 可以很好地解决这些问题。
Immutable
immutable 数据一种利用结构共享形成的持久化数据结构,一旦有部分被修改,那么将会返回一个全新的对象,并且原来相同的节点会直接共享。
每次修改一个 immutable 对象时都会创建一个新的不可变的对象,在新对象上操作并不会影响到原对象的数据。 immutable 对象数据内部采用是多叉树的结构,如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。如图:
优点
Immutable 能够完整记录历史的状态,因此我们不用担心 Immutable 数据会在某些地方被改变。它就像一个“快照”,你只要保留它,就能在之后的任何地方访问到历史的记录。
a === b; // false a.get('filter') === b.get('filter'); // true
// 将对象转为 Immutable List 数据结构'; const list1 = List([ 1, 2 ]);
// Immutable List 的方法,类似于 js array const list2 = list1.push(3, 4, 5); // [1,2,3,4,5] const list3 = list2.unshift(0); // [0,1,2,3,4,5] const list4 = list1.concat(list2, list3); // [1,2,3,4,5,0,1,2,3,4,5] //push, set, unshift or splice 都可以直接用,返回一个新的immutable对象
const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } });// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
//如果取一级属性 直接通过get方法,如果取多级属性 getIn(["a","b","c"]]) console.log(nested2.getIn([ 'a', 'b', 'd' ])); // 6
// setIn 设置新的值const nested3 = nested2.setIn([ 'a', 'b', 'd' ], "kerwin");// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: "kerwin" } } }
// updateIn 回调函数更新 const nested3 = nested2.updateIn([ 'a', 'b', 'd' ], value => value + 1); console.log(nested3);// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } } const nested4 = nested3.updateIn([ 'a', 'b', 'c' ], list => list.push(6));// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
Redux + Immutable
未使用immutable时,一旦当newStateList中的类型较为复杂(包含引用类型),且需要修改newStateList时,就会发生报错,因为
[...xxx, ...xxx]
是浅拷贝,会影响原来的状态。 Redux 结合 Immutable 使用,通过store中传递过来的老状态prevState先转化为immutable对象,对深拷贝之后的对象,再进行修改等操作时,不会影响原状态,最后再通过toJS()转换为js对象即可。