You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
475 lines
17 KiB
475 lines
17 KiB
6 years ago
|
Immutable collections for JavaScript
|
||
|
====================================
|
||
|
|
||
|
[![Build Status](https://travis-ci.org/facebook/immutable-js.svg)](https://travis-ci.org/facebook/immutable-js)
|
||
|
|
||
|
[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 presents
|
||
|
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`.
|
||
|
|
||
|
Want to hear more? Watch the presentation about Immutable.js:
|
||
|
|
||
|
<a href="https://youtu.be/I7IdS-PbEgI" target="_blank" alt="Immutable Data and React"><img src="https://img.youtube.com/vi/I7IdS-PbEgI/0.jpg" /></a>
|
||
|
|
||
|
[Persistent]: http://en.wikipedia.org/wiki/Persistent_data_structure
|
||
|
[Immutable]: http://en.wikipedia.org/wiki/Immutable_object
|
||
|
[hash maps tries]: http://en.wikipedia.org/wiki/Hash_array_mapped_trie
|
||
|
[vector tries]: http://hypirion.com/musings/understanding-persistent-vector-pt-1
|
||
|
|
||
|
|
||
|
Getting started
|
||
|
---------------
|
||
|
|
||
|
Install `immutable` using npm.
|
||
|
|
||
|
```shell
|
||
|
npm install immutable
|
||
|
```
|
||
|
|
||
|
Then require it into any module.
|
||
|
|
||
|
```javascript
|
||
|
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
|
||
|
```
|
||
|
|
||
|
### Browser
|
||
|
|
||
|
To use `immutable` from a browser, download [dist/immutable.min.js](https://github.com/facebook/immutable-js/blob/master/dist/immutable.min.js)
|
||
|
or use a CDN such as [CDNJS](https://cdnjs.com/libraries/immutable)
|
||
|
or [jsDelivr](http://www.jsdelivr.com/#!immutable.js).
|
||
|
|
||
|
Then, add it as a script tag to your page:
|
||
|
|
||
|
```html
|
||
|
<script src="immutable.min.js"></script>
|
||
|
<script>
|
||
|
var map1 = Immutable.Map({a:1, b:2, c:3});
|
||
|
var map2 = map1.set('b', 50);
|
||
|
map1.get('b'); // 2
|
||
|
map2.get('b'); // 50
|
||
|
</script>
|
||
|
```
|
||
|
|
||
|
Or use an AMD loader (such as [RequireJS](http://requirejs.org/)):
|
||
|
|
||
|
```javascript
|
||
|
require(['./immutable.min.js'], function (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
|
||
|
});
|
||
|
```
|
||
|
|
||
|
If you're using [browserify](http://browserify.org/), the `immutable` npm module
|
||
|
also works from the browser.
|
||
|
|
||
|
### TypeScript
|
||
|
|
||
|
Use these Immutable collections and sequences as you would use native
|
||
|
collections in your [TypeScript](http://typescriptlang.org) programs while still taking
|
||
|
advantage of type generics, error detection, and auto-complete in your IDE.
|
||
|
|
||
|
Just add a reference with a relative path to the type declarations at the top
|
||
|
of your file.
|
||
|
|
||
|
```javascript
|
||
|
///<reference path='./node_modules/immutable/dist/immutable.d.ts'/>
|
||
|
import Immutable = require('immutable');
|
||
|
var map1: Immutable.Map<string, number>;
|
||
|
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. Developing with immutable data encourages you to think
|
||
|
differently about how data flows through your application.
|
||
|
|
||
|
Subscribing to data events throughout your application creates a huge overhead of
|
||
|
book-keeping which can hurt performance, sometimes dramatically, and creates
|
||
|
opportunities for areas of your application to get out of sync with each other
|
||
|
due to easy to make programmer error. Since immutable data never changes,
|
||
|
subscribing to changes throughout the model is a dead-end and new data can only
|
||
|
ever be passed from above.
|
||
|
|
||
|
This model of data flow aligns well with the architecture of [React][]
|
||
|
and especially well with an application designed using the ideas of [Flux][].
|
||
|
|
||
|
When data is passed from above rather than being subscribed to, and you're only
|
||
|
interested in doing work when something has changed, you can use equality.
|
||
|
|
||
|
Immutable collections should be treated as *values* rather than *objects*. 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 the `===` operator which determines object reference identity.
|
||
|
|
||
|
```javascript
|
||
|
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.is` and `.equals()` as a performance optimization.
|
||
|
|
||
|
If an object is immutable, it can be "copied" simply by making 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).
|
||
|
|
||
|
```javascript
|
||
|
var map1 = Immutable.Map({a:1, b:2, c:3});
|
||
|
var clone = map1;
|
||
|
```
|
||
|
|
||
|
[React]: http://facebook.github.io/react/
|
||
|
[Flux]: http://facebook.github.io/flux/docs/overview.html
|
||
|
|
||
|
|
||
|
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][].
|
||
|
|
||
|
[ES6]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla
|
||
|
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||
|
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
||
|
[Set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
|
||
|
|
||
|
The difference for the immutable collections is that methods which would mutate
|
||
|
the collection, like `push`, `set`, `unshift` or `splice` instead return a new
|
||
|
immutable collection. Methods which return new arrays like `slice` or `concat`
|
||
|
instead return new immutable collections.
|
||
|
|
||
|
```javascript
|
||
|
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 on `Immutable.Set`, including collection operations like `forEach()`
|
||
|
and `map()`.
|
||
|
|
||
|
```javascript
|
||
|
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.
|
||
|
|
||
|
Designed to inter-operate with your existing JavaScript, `immutable`
|
||
|
accepts plain JavaScript Arrays and Objects anywhere a method expects an
|
||
|
`Iterable` with no performance penalty.
|
||
|
|
||
|
```javascript
|
||
|
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, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }
|
||
|
```
|
||
|
|
||
|
This is possible because `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. Because Seq evaluates lazily and does not cache intermediate
|
||
|
results, these operations can be extremely efficient.
|
||
|
|
||
|
```javascript
|
||
|
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
|
||
|
JavaScript Object properties are always strings, even if written in a quote-less
|
||
|
shorthand, while Immutable Maps accept keys of any type.
|
||
|
|
||
|
```js
|
||
|
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 JavaScript Objects first converts the key to a string, but
|
||
|
since Immutable Map keys can be of any type the argument to `get()` is
|
||
|
not altered.
|
||
|
|
||
|
|
||
|
### Converts back to raw JavaScript objects.
|
||
|
|
||
|
All `immutable` Iterables can be converted to plain JavaScript 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.
|
||
|
|
||
|
```javascript
|
||
|
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":2,"c":[3,4,5]}'
|
||
|
```
|
||
|
|
||
|
### Embraces ES6
|
||
|
|
||
|
`Immutable` takes advantage of features added to JavaScript in [ES6][],
|
||
|
the latest standard version of ECMAScript (JavaScript), including [Iterators][],
|
||
|
[Arrow Functions][], [Classes][], and [Modules][]. It's also inspired by the
|
||
|
[Map][] and [Set][] collections added to ES6. The library is "transpiled" to ES3
|
||
|
in order to support all modern browsers.
|
||
|
|
||
|
All examples are presented in ES6. To run in all browsers, they need to be
|
||
|
translated to ES3.
|
||
|
|
||
|
```js
|
||
|
// ES6
|
||
|
foo.map(x => x * x);
|
||
|
// ES3
|
||
|
foo.map(function (x) { return x * x; });
|
||
|
```
|
||
|
|
||
|
[Iterators]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/The_Iterator_protocol
|
||
|
[Arrow Functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
|
||
|
[Classes]: http://wiki.ecmascript.org/doku.php?id=strawman:maximally_minimal_classes
|
||
|
[Modules]: http://www.2ality.com/2014/09/es6-modules-final.html
|
||
|
|
||
|
|
||
|
Nested Structures
|
||
|
-----------------
|
||
|
|
||
|
The collections in `immutable` are intended to be nested, allowing for deep
|
||
|
trees of data, similar to JSON.
|
||
|
|
||
|
```javascript
|
||
|
var nested = Immutable.fromJS({a:{b:{c:[3,4,5]}}});
|
||
|
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
|
||
|
```
|
||
|
|
||
|
A few power-tools allow for reading and operating on nested data. The
|
||
|
most useful are `mergeDeep`, `getIn`, `setIn`, and `updateIn`, found on `List`,
|
||
|
`Map` and `OrderedMap`.
|
||
|
|
||
|
```javascript
|
||
|
var nested2 = nested.mergeDeep({a:{b:{d:6}}});
|
||
|
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
|
||
|
```
|
||
|
|
||
|
```javascript
|
||
|
nested2.getIn(['a', 'b', 'd']); // 6
|
||
|
|
||
|
var nested3 = nested2.updateIn(['a', 'b', 'd'], value => value + 1);
|
||
|
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }
|
||
|
|
||
|
var nested4 = nested3.updateIn(['a', 'b', 'c'], list => list.push(6));
|
||
|
// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
|
||
|
```
|
||
|
|
||
|
|
||
|
Lazy Seq
|
||
|
--------
|
||
|
|
||
|
`Seq` describes a lazy operation, allowing them to efficiently chain
|
||
|
use of all the Iterable methods (such as `map` and `filter`).
|
||
|
|
||
|
**Seq is immutable** — Once a Seq is created, it cannot be
|
||
|
changed, appended to, rearranged 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.
|
||
|
|
||
|
For example, the following does not perform any work, because the resulting
|
||
|
Seq is never used:
|
||
|
|
||
|
var oddSquares = Immutable.Seq.of(1,2,3,4,5,6,7,8)
|
||
|
.filter(x => x % 2).map(x => x * x);
|
||
|
|
||
|
Once the Seq is used, it performs only the work necessary. In this
|
||
|
example, no intermediate arrays are ever created, filter is called three times,
|
||
|
and map is only called twice:
|
||
|
|
||
|
console.log(oddSquares.get(1)); // 9
|
||
|
|
||
|
Any collection can be converted to a lazy Seq with `.toSeq()`.
|
||
|
|
||
|
var seq = Immutable.Map({a:1, b:1, c:1}).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(key => key.toUpperCase()).flip().toObject();
|
||
|
// Map { A: 1, B: 1, C: 1 }
|
||
|
|
||
|
As well as expressing logic that would otherwise seem memory-limited:
|
||
|
|
||
|
Immutable.Range(1, Infinity)
|
||
|
.skip(1000)
|
||
|
.map(n => -n)
|
||
|
.filter(n => n % 2 === 0)
|
||
|
.take(2)
|
||
|
.reduce((r, n) => r * n, 1);
|
||
|
// 1006008
|
||
|
|
||
|
Note: An iterable is always iterated in the same order, however that order may
|
||
|
not always be well defined, as is the case for the `Map`.
|
||
|
|
||
|
|
||
|
Equality treats Collections as Data
|
||
|
-----------------------------------
|
||
|
|
||
|
`Immutable` provides equality which treats immutable data structures as pure
|
||
|
data, performing a deep equality check if necessary.
|
||
|
|
||
|
```javascript
|
||
|
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 instances
|
||
|
assert(Immutable.is(map1, map2)); // have equivalent values
|
||
|
assert(map1.equals(map2)); // alternatively use the equals method
|
||
|
```
|
||
|
|
||
|
`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.
|
||
|
|
||
|
[Object.is]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
||
|
|
||
|
|
||
|
Batching Mutations
|
||
|
------------------
|
||
|
|
||
|
> If a tree falls in the woods, does it make a sound?
|
||
|
>
|
||
|
> If a pure function mutates some local data in order to produce an immutable
|
||
|
> return value, is that ok?
|
||
|
>
|
||
|
> — Rich Hickey, Clojure
|
||
|
|
||
|
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 performant 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.
|
||
|
|
||
|
```javascript
|
||
|
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, which could result in undesired behavior.
|
||
|
|
||
|
*Important!*: Only a select few methods can be used in `withMutations` including
|
||
|
`set`, `push` and `pop`. These methods can be applied directly against a
|
||
|
persistent data-structure where other methods like `map`, `filter`, `sort`,
|
||
|
and `splice` will always return new immutable data-structures and never mutate
|
||
|
a mutable collection.
|
||
|
|
||
|
|
||
|
Documentation
|
||
|
-------------
|
||
|
|
||
|
[Read the docs](http://facebook.github.io/immutable-js/docs/) and eat your vegetables.
|
||
|
|
||
|
Docs are automatically generated from [Immutable.d.ts](https://github.com/facebook/immutable-js/blob/master/type-definitions/Immutable.d.ts).
|
||
|
Please contribute!
|
||
|
|
||
|
Also, don't miss the [Wiki](https://github.com/facebook/immutable-js/wiki) which
|
||
|
contains articles on specific topics. Can't find something? Open an [issue](https://github.com/facebook/immutable-js/issues).
|
||
|
|
||
|
|
||
|
Testing
|
||
|
-------
|
||
|
|
||
|
If you are using the [Chai Assertion Library](http://chaijs.com/), [Chai Immutable](https://github.com/astorije/chai-immutable) provides a set of assertions to use against `Immutable` collections.
|
||
|
|
||
|
|
||
|
Contribution
|
||
|
------------
|
||
|
|
||
|
Use [Github issues](https://github.com/facebook/immutable-js/issues) for requests.
|
||
|
|
||
|
We actively welcome pull requests, learn how to [contribute](./CONTRIBUTING.md).
|
||
|
|
||
|
|
||
|
Changelog
|
||
|
---------
|
||
|
|
||
|
Changes are tracked as [Github releases](https://github.com/facebook/immutable-js/releases).
|
||
|
|
||
|
|
||
|
Thanks
|
||
|
------
|
||
|
|
||
|
[Phil Bagwell](https://www.youtube.com/watch?v=K2NYwP90bNs), for his inspiration
|
||
|
and research in persistent data structures.
|
||
|
|
||
|
[Hugh Jackson](https://github.com/hughfdjackson/), for providing the npm package
|
||
|
name. If you're looking for his unsupported package, see [this repository](https://github.com/hughfdjackson/immutable).
|
||
|
|
||
|
|
||
|
License
|
||
|
-------
|
||
|
|
||
|
Immutable.js is [MIT-licensed](https://github.com/facebook/immutable-js/blob/master/LICENSE).
|