ImmutableJS 入门教学

ImmutableJS

前言

一般来说在 JavaScript 中有两种资料类型:Primitive(String、Number、Boolean、null、undefinded)和 Object(Reference)。在 JavaScript 中物件的操作比起 Java 容易很多,但也因为相对弹性不严谨,所以产生了一些问题。在 JavaScript 中的 Object(物件)资料是 Mutable(可以变的),由于是使用 Reference 的方式,所以当修改到复制的值也会修改到原始值。例如下面的 map2 值是指到 map1,所以当 map1 值一改,map2 的值也会受影响。

var map1 = { a: 1 }; 
var map2 = map1; 
map2.a = 2

通常一般作法是使用 deepCopy 来避免修改,但这样作法会产生较多的资源浪费。为了很好的解决这个问题,我们可以使用 Immutable Data,所谓的 Immutable Data 就是一旦建立,就不能再被修改的数据资料。

为了解决这个问题,在 2013 年时 Facebook 工程师 Lee Byron 打造了 ImmutableJS,但并没有被预设放到 React 工具包中(虽然有提供简化的 Helper),但 ImmutableJS 的出现确实解决了 React 甚至 Redux 所遇到的一些问题。

以下范例即是引入了 ImmutableJS 的效果,读者可以发现,虽然我们操作了 map1 的值,但会发现原本的 map1 并未受到影响(因为任何修改都不会影响到原始资料),虽然使用 deepCopy 也可以模拟类似的效果但会浪费过多的计算资源和记忆体,ImmutableJS 则可以容易地共享没有被修该到的资料(例如下面的资料 b 即为 map1map2 共享),因而有更好的效能表现。

import Immutable from 'immutable';

var map1 = Immutable.Map({ a: 1, b: 3 });
var map2 = map1.set('a', 2);

map1.get('a'); // 1
map2.get('a'); // 2

ImmutableJS 特性介绍

ImmutableJS 提供了 7 种不可修改的资料类型:ListMapStackOrderedMapSetOrderedSetRecord。若是对 Immutable 物件操作都会回传一个新值。其中比较常用的有 ListMapSet

  1. Map:类似于 key/value 的 object,在 ES6 也有原生 Map 对应

`javascript const Map= Immutable.Map;

const map1 = Map({ a: 1 }); map1.size

const map2 = map1.set('a', 7);

const map3 = map1.delete('a');

const map4 = map1.clear();

const map5 = map1.update('a', () => (7))

const map6 = Map({ b: 3 }); map1.merge(map6); `

  1. List:有序且可以重复值,对应于一般的 Array

`javascript const List= Immutable.List;

const arr1 = List([1, 2, 3]); arr1.size

const arr2 = arr1.set(-1, 7); const arr3 = arr1.set(4, 0);

const arr4 = arr1.delete(1);

const arr5 = arr1.insert(1, 2);

const arr6 = arr1.clear(); `

  1. Set:没有顺序且不能重复的列表

`javascript const Set= Immutable.Set;

const set1 = Set([1, 2, 3]);

const set2 = set1.add(1).add(5);

const set3 = set1.delete(3);

const set4 = Set([2, 3, 4, 5, 6]); set1.union(set4);

set1.intersect(set4);

set1.subtract(set4); `

ImmutableJS 的特性整理

  1. Persistent Data Structure 在 ImmutableJS 的世界里,只要资料一被创建,就不能修改,维持 Immutable。就不会发生下列的状况:

`javascript var obj = { a: 1 };

funcationA(obj); console.log(obj.a) `

使用 ImmutableJS 就没有这个问题:

`javascript

const $obj = fromJS({ a: 1 });

funcationA($obj); console.log($obj.get('a')) `

  1. Structural Sharing 为了维持资料的不可变,又要避免像 deepCopy 一样复制所有的节点资料而造成的资源损耗,在 ImmutableJS 使用的是 Structural Sharing 特性,亦即如果物件树中一个节点发生变化的话,只会修改这个节点和和受它影响的父节点,其他节点则共享。

`javascript const obj = {

count: 1,
list: [1, 2, 3, 4, 5]

} var map1 = Immutable.fromJS(obj); var map2 = map1.set('count', 4);

console.log(map1.list === map2.list); `

  1. Support Lazy Operation

`javascript Immutable.Range(1, Infinity) .map(n => -n)

Immutable.Range(1, Infinity) .map(n => -n) .take(2) .reduce((r, n) => r + n, 0); `

  1. 丰富的 API 并提供快速转换原生 JavaScript 的方式 在 ImmutableJS 中可以使用 fromJS()toJS() 进行 JavaScript 和 ImmutableJS 之间的转换。但由于在转换之间会非常耗费资源,所以若是你决定引入 ImmutableJS 的话请尽量维持资料处在 Immutable 的状态。

  2. 支持 Functional Programming Immutable 本身就是 Functional Programming(函数式程式设计)的概念,所以在 ImmutableJS 中可以使用许多 Functional Programming 的方法,例如:mapfiltergroupByreducefindfindIndex 等。

  3. 容易实现 Redo/Undo 历史回顾

React 效能优化

ImmutableJS 除了可以和 Flux/Redux 整合外,也可以用于基本 react 效能优化。以下是一般使用效能优化的简单方式:

传统 JavaScript 比较方式,若资料型态为 Primitive 就不会有问题:

// 在 shouldComponentUpdate 比较接下来的 props 是否一致,若相同则不重新渲染,提升效能
shouldComponentUpdate (nextProps) {
    return this.props.value !== nextProps.value;
}

但当比较的是物件的话就会出现问题:

// 假设 this.props.value 为 { foo: 'app' }
// 假设 nextProps.value 为 { foo: 'app' },
// 虽然两者值是一样,但由于 reference 位置不同,所以视为不同。但由于值一样应该要避免重复渲染
this.props.value !== nextProps.value; // true

使用 ImmutableJS

var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'app'  });
var y = x.set('foo', 'azz');
x === y; // false

在 ES6 中可以使用官方文件上的 PureRenderMixin 进行比较,可以让程式码更简洁:

import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }
  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

总结

虽然 ImmutableJS 的引入可以带来许多好处和效能的提升但由于引入整体档案较大且较具侵入性,在引入之前可以自行评估看看是否合适于目前的专案。接下来我们将在后面的章节讲解如何将 ImmutableJSRedux 整合应用到实务上的范例。

延伸阅读

  1. 官方网站
  2. Immutable.js初识
  3. Immutable 详解及 React 中实践
  4. 为什么需要Immutable.js
  5. facebook immutable.js 意义何在,使用场景?
  6. React 巢状 Component 效能优化
  7. PureRenderMixin
  8. seamless-immutable
  9. Immutable Data Structures and JavaScript

(image via risingstack

| | 上一章:React Router 入门实战教学 | 下一章:Flux 基础概念与实战入门 |

| 勘误、提问或许愿 |



相关npm包集合



相关站点资源