-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Upgrading to Immutable v3
Immutable v3.0.0 is here, and there are a number of new concepts, renamed methods, and other breaking changes. If you've already been using Immutable v2.x you should be aware of the major changes before upgrading your codebase.
Immutable aims to be Idiomatic JavaScript. More than that, it tries to mirror existing related ES6 specifications. It expands on these specs while attempting to maintain the spirit of these specs.
For example, the APIs of Immutable.Map
and Immutable.Set
should look very similar to the existing ES6 Map and Set.
This philosophy helped guide the changes to Immutable v3.
Some existing concepts in v2 have been renamed in v3:
Where we used to refer to objects with iterators and collection methods as Sequence
s, we now refer to them as Iterable
s. JavaScript Arrays and Objects which we used to refer as "seqable" we now refer to as "iterable-like".
This is both more aligned to the vernacular used in ES6, and avoids confusion with the lazy Seq
.
List
is the same indexed, ordered, dense homogeneous collection previously known as Vector
.
Based on the spirit of ES6's Map
and Set
named by the abstract type of collection rather than the specific implemented data structure (e.g., they are not named HashMap
or TreeSet
); Vector
is the name of a specific data structure, whereas List
is the type of collection we're describing.
In order to better match the property defined on ES6 Map
and Set
, Immutable v3 has renamed length
to size
.
Because length
is pretty annoying to code-mod, a warning is logged if you attempt to access length
directly. This warning can be disabled with Immutable.Iterable.noLengthWarning = true;
. The warning will be removed in a future version.
In v2, we had Sequence
and IndexedSequence
. Iterable
now comes in three specific flavors, describing the behavior of the "key" property in enumeration as well as providing relevant additional methods:
Map
and OrderedMap
extend from this.
Returns true when passed to Iterable.isKeyed(maybeKeyed)
or Iterable.isAssociative(maybeAssociative)
.
Keyed iterables have keys with any value associated directly with each value. If you scramble a keyed iterable, each key and value pair will remain together.
List
and Stack
extend from this.
Returns true when passed to Iterable.isIndexed(maybedIndexed)
or Iterable.isAssociative(maybedAssociative)
.
Indexed iterables have numeric keys which always incrementally count up from 0 as they are enumerated and are not directly-associated to their corresponding value. If you scramble an indexed iterable, the resulting iterable's keys would still count up from 0.
Set
extends from this.
Returns false when passed to either Iterable.isKeyed(maybeKeyed)
, Iterable.isIndexed(maybedIndexed)
or Iterable.isAssociative(maybedAssociative)
.
Set iterables have no keys at all. When enumerated, the second argument is also the value.
Perhaps the biggest conceptual change is that all collection operations (filter, map, take, skip, slice, etc.) are no longer Lazy by default. While laziness is powerful, it can also be surprising. This pitfall has been a constant point of confusion.
In Immutable v2.x.x, a common pattern is to follow a collection operation with a .toXXX()
method to convert back into the original type.
// Immutable v2
myMap.filter(somePredicate)
// Seq { ... }
myMap.filter(somePredicate).toMap()
// Map { ... }
In Immutable v3, operations like this are eager, so the explicit conversion is no longer necessary.
// Immutable v3
myMap.filter(somePredicate)
// Map { ... }
Lazy operations are still possible, and in cases where more than one collection method are used together, or the end result should be of a different type, it can result in a performance improvement by removing intermediate allocations.
Lazy operations on Iterables
are described by a Seq
. In order to perform lazy operations on a collection, you must first convert it to a Seq
with .toSeq()
. This is a very inexpensive O(1) operation.
// Immutable v2
myMap.filter(somePred).sort(someComp).toOrderedMap()
// OrderedMap { ... }
In v3, this might produce the same result, however intermediate Immutable Map
s are being produced after filter(somePred)
and sort(someComp)
. In fact, since the iteration order of a Map
is undefined, sort(someComp)
may not produce anything sorted, as the conversion back to Map could disrupt the sorted order.
// Immutable v3
myMap.toSeq().filter(somePred).sort(someComp).toOrderedMap()
// OrderedMap { ... }
Here, calling toSeq()
before the collection methods ensures that each returns another lazy Seq
. The chain of operations will not be executed until the conversion to toOrderedMap()
forces them.
Seq
comes in the same three flavors as Iterable
: KeyedSeq
, IndexedSeq
, and SetSeq
. All Iterables have the methods toKeyedSeq()
, toIndexedSeq()
and toSetSeq()
which allow for conversion between types.
Example: Want a Map
of value-to-index in a List
for quick lookup?
var lookupMap = myList.toKeyedSeq().flip().toMap();
Immutable v2 allowed you to explicitly create collections from iterables with CollectionType.from(iterableLike)
, or attempt to intelligently do so by calling CollectionType(x, y, z)
directly, which could lead to ambiguity when only one argument is provided.
In an effort to better mirror ES6 collection constructors, Immutable v3 collections no longer have ambiguous constructors, nor do they have from()
or empty()
methods. Indexed and Set constructors do provide of(...values)
.
// Immutable v2
Vector([1,2,3]);
// Vector [ 1, 2, 3 ]
Vector(1,2,3);
// Vector [ 1, 2, 3 ]
Vector.from([1,2,3]);
// Vector [ 1, 2, 3 ]
Vector.empty();
// Vector []
In v3, these same constructions look like:
// Immutable v3
List([1,2,3]);
// List [ 1, 2, 3 ]
List.of(1,2,3);
// List [ 1, 2, 3 ]
List([1,2,3]);
// List [ 1, 2, 3 ]
List();
// List []
These new strict constructors are a prerequisite to enable eager collection operations, and make them easier to use along-side transducer libraries.
Cursors are no longer part of the core Immutable library, however the existing implementation lives on as the first addition to the contrib/
directory.
Also, the API for constructing Cursors has changed:
// Immutable v2
var myCursor = myMap.cursor(key, onChange);
var fooCursor = myCursor.get('foo');
The contrib Cursors are constructed directly:
// Immutable v3
var Cursor = require('immutable/contrib/cursor');
var myCursor = Cursor.from(myMap, key, onChange);
var fooCursor = myCursor.get('foo');
The included Cursor implementation does not deserve first-class treatment. I hope to see this open up development of other state-management APIs.
The included implementation of Cursor has no internal dependencies, illustrating how you can extend Immutable in your own libraries.
-
vector.remove(i)
is now equivalent tovector.splice(i, 1)
, better mirroring the APIs of dense list collections found in other libraries. -
XXX.isXXX()
predicates exist on all major Iterable types.Iterable.isIterable()
Seq.isSeq()
Map.isMap()
OrderedMap.isOrderedMap()
List.isList()
Stack.isStack()
Set.isSet()
-
groupBy()
andcountBy()
return concreteMap
. - Added:
keyOf()
andlastKeyOf()
on KeyedIterable are similar toindexOf()
andlastIndexOf()
on IndexedIterable.