ImmutableJS Introduction

Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data presetns a mutative API which does not update the data in-place, but instead always yields new updated data.

Immutable.js provides many Persistent Immutable data structures including: List, Stack, Map, OrderedMap, Set, OrderedSet and Record.

These data structures are highly efficient on modern JavaScript VMs by using structural sharing via ‘hash maps tries’ and ‘vector tries’ as popularized by Clojure and Scala, minimizing the need to copy or cache data.

Immutable also provides a lazy Seq, allowing efficient chaining of collection methods like map and filter without creating intermediate representations. Create some Seq with Range and Repeat.

Getting Started

npm install Immutable

var Immutable = require('immutable'); var map1 = Immutable.Map({a:1,b:2,c:3}); var map2 = map1.set('b',50); map1.get('b'); //2 map2.get('b'); //50

The case for Immutability

Much of what makes application development difficult is tracking mutation and maintaining state. Development with immutable data encourages you to think differently about how data flows through your application.

Subscribing to data event throughout your application.

Immutable data never change.

Immutable collections should be treated as value rather than object. While objects represents some thing which could change over time, a value represents the state of that thing at a particular instance of time. This principle is most important to understanding the appropriate use of immutable data. In order to treat Immutable.js collections as values, it’s important to use the Immutable.is() function or .equals() method to determine value equality instead of === operator which determines object reference identity.

var map1 = Immutable.Map({a:1,b:2,c:3}); var map2 = map1.set('b',2); assert(map1.equals(map2) === true); var map3 = map1.set('b',50); assert(map1.equals(map3) === false);

Note: As a performance optimization Immutable attempts to return the existing collection when an operation would result in an identical collection, allowing for using === reference equality to determine if something definitely has not changed. This can be extremely useful when used within memoization function which would prefer to re-run the function if a deeper equality check could potentially be more costly. The === equality check is also used internally by Immutable.js and equals() as a performance as a performance optimization.

If an object is immutable, it can be ‘copied’ simply by marking another reference to it instead of copying the entire object. Because a reference is much smaller than the object itself, this results in memory savings and a potential boost in execution speed for programs which rely on copies(such as an undo-stack).

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

JavaScript-first API

While immutable is inspired by Clojure, Scala, Haskell and other functional programming environments, it’s designed to bring these powerful concepts to JavaScript, and therefore has an Object-Oriented API that closely mirrors that of ES6 Array, Map, and Set.

The difference for the immutable collections is that methods which would mutate the collection, like push, set, unshift, or splice instead of return a new immutable collection. Methods which return new array like slice or concat instead return new immutable collections.

var list1 = Immutable.List.of(1,2); var list2 = list1.push(3,4,5); var list3 = list2.unshift(0); var list4 = list1.concat(list2,list3); assert(list1.size === 2); assert(list2.size === 5); assert(list3.size === 6); assert(list4.size === 13); assert(list4.get(0) === 1);

Almost all of the methods on Array will be found in similar form on Immutable.List, those of Map found on Immutable.Map and those of Set found onImmutable.Set, including collection operation like forEach(), and map().

var alpha = Immutable.Map({a:1,b:2,c:3,d:4}); alpha.map((v,k) => k.toUpperCase()).join(); // 'A,B,C,D'

Accepts raw JavaScript objects

‘immutable’ accepts plain JavaScript Arrays and Objects anywhere a mehtod expects an Iterable with no performance penalty.

var map1 = Immutable.Map({a:1,b:2,c:3,d:4}); var map2 = Immutable.Map({c:10,a:20,t:30}); var obj = {d:100,o:200,g:300}; var map3 = map1.merge(map2,obj); // Map {a:20,b2,c:10,d:100,t:30,o:200,g:300}

Immutable can treat any JavaScript Array or Object as an Iterable. You can take advantage of this in order to get sophisticated collection methods on JavaScript Objects, which otherwise have a very sparse native API.

var myObject = {a:1,b:2,c:3}; Immutable.Seq(myObject).map(x=>x*x).toObject(); // {a:1,b:4,c:9};

Keep in Mind, when using JS objects to construct Immutable Maps, that JS Object properties are always strings, even if written in a quote-less shorthand, while Immutable Map accept keys of any type.

1
2
3
4
5
6
7
8
  var obj = {1: 'one'};
Object.keys(obj); // ['1']
obj[1]; // 'one'
obj['1']; // 'one'

var map = Immutable.fromJS(obj);
map.get('1'); // 'one'
map.get(1); // undefined

Property access for JS Object first converts the key to a string, but since Immutable Map keys can be of any type the argument to get() is not altered.
Namely Immutable Map will treat ‘1’ and 1 differently.

Converts Back to Raw JS Obejcts

All Immutable Iterable can be converted to plain JS Arrays and Objects shallowly with toArray() and toObject() or deeply with toJS(). All Immutable Iterables also implement toJSON() allowing them to be passed to JSON.stringify() directly.

var deep = Immutable.Map({a:1,b:2,c:Immutable.List.of(3,4,5)}); deep.toObject() // {a:1,b:2,c:List [3,4,5]} deep.toArray() // [1,2,List [3,4,5]] deep.toJS() // {a:1,b:2,c:[3,4,5]} JSON.stringify(deep) // '{"a":1,"b","c":[3,4,5]}'

Embraces ES6

Immutable takes advantage of features added to JS ES6, the latest standard version of ECMAScript, including Interators,Arrow Function, Classes, and Modules.

Nested Structures

The collections in immutable are intended to be nested, allowing for deep trees of data, similar to JSON

var nested = Immutable.fromJS({a:{b:{c:[3,4,5]}}}) // Map{ a: Map { b: Map { c: List [3,4,5]}}}
A few power-tool allow for reading and operating on nested data. The most useful are mergeDeep, getIn, setIn and updateIn found on List, Map and OrderedMap

1
2
3
4
5
6
7
8
  var nested2 = nested.mergeDeep({a:{b:{d:6}}})
// Map {a: Map { b: { c: List [3,4,5], d: 6}}}

nested2.getIn(['a','b','d']); //6

var nested3 = nested2.updateIn(['a','b','d'], value => value + 1); d => 7

var nest4 = nested3.updateIn(['a','b','c'], list => list.push(6)); c => List [3,4,5,6]

Lazy Seq

Seq describes a lazy operation, allowing them to efficiently chain use all the Iterable methods.(such as map and filter)

Seq is immutable – Once a Seq is created it cannot be changed, appended to, rerranged or otherwise modified. Instead, any mutative method called on a Seq will return a new Seq.

Seq is lazy – Seq does as little work as necessary to respond to any method call.

Once the Seq is used, it performs only the work necessary.

Any collection can be converted to a lazy Seq with toSeq().

var seq = Immutable.Map({a:1,b:2,c:3}).toSeq();

Seq allow for the efficient chaining of sequence operations, especially when converting to a different concrete type(such as to a JS Object)

seq.flip().map(k => k.toUpperCase()).flip().toObject(); // Map {A: 1, B:2, C:3}

Equality treats Collection as Data

Immutable provides equality which treats immutable data structures as pure data, performing a deep equality check if necessary.

var map1 = Immutable.Map({a:1,b:1,c:1}); var map2 = Immutable.Map({a:1,b:1,c:1}); assert(map1 !== map2) // two different instance, not same address assert(Immutable.is(map1,map2)); // have equivalent values assert(map1.equals(map2)); // alternatively use the equals methods.

Immutable.is() uses the same measure of equality as Object.is including if both are immutable and all keys and values are equal using the same measure of equality.

Batching Mutations

Applying a mutation to create a new immutable object results in some overhead, which can add up to a minor performance penalty. If you need to apply a series of mutations locally before returning, Immutable gives you the ability to create a temporary mutable(transient) copy of a collection and apply a batch of mutations in a performance manner by using withMutations. In fact, this is exactly how Immutable applies complex mutations itself.

As an example, building list2 results in the creation of 1 , not 3, new immutable Lists
var list1 = Immutable.List.of(1,2,3); var list2 = list1.withMutations(function(list){ list.push(4).push(5).push(6) }); assert(list1.size === 3); assert(list2.size === 6);

Note: Immutable also provides asMutable and asImmutable, but only encourages their use when withMutations will not suffice. Use caution to not return a mutable copy could result in undesired behavior.

important: Only a select few method can be used in withMutations including set, push, pop. These methods can be applied directly against a persistent data-structure where other methods like map,filter,sortand splice will always return new immutable data-structure and never mutate a mutable collection.

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×