[0c6b92a] | 1 | # Immutable collections for JavaScript
|
---|
| 2 |
|
---|
| 3 | [![Build Status](https://github.com/immutable-js/immutable-js/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/immutable-js/immutable-js/actions/workflows/ci.yml?query=branch%3Amain) [Chat on slack](https://immutable-js.slack.com)
|
---|
| 4 |
|
---|
| 5 | [Read the docs](https://immutable-js.com/docs/) and eat your vegetables.
|
---|
| 6 |
|
---|
| 7 | Docs are automatically generated from [README.md][] and [immutable.d.ts][].
|
---|
| 8 | Please contribute! Also, don't miss the [wiki][] which contains articles on
|
---|
| 9 | additional specific topics. Can't find something? Open an [issue][].
|
---|
| 10 |
|
---|
| 11 | **Table of contents:**
|
---|
| 12 |
|
---|
| 13 | - [Introduction](#introduction)
|
---|
| 14 | - [Getting started](#getting-started)
|
---|
| 15 | - [The case for Immutability](#the-case-for-immutability)
|
---|
| 16 | - [JavaScript-first API](#javascript-first-api)
|
---|
| 17 | - [Nested Structures](#nested-structures)
|
---|
| 18 | - [Equality treats Collections as Values](#equality-treats-collections-as-values)
|
---|
| 19 | - [Batching Mutations](#batching-mutations)
|
---|
| 20 | - [Lazy Seq](#lazy-seq)
|
---|
| 21 | - [Additional Tools and Resources](#additional-tools-and-resources)
|
---|
| 22 | - [Contributing](#contributing)
|
---|
| 23 |
|
---|
| 24 | ## Introduction
|
---|
| 25 |
|
---|
| 26 | [Immutable][] data cannot be changed once created, leading to much simpler
|
---|
| 27 | application development, no defensive copying, and enabling advanced memoization
|
---|
| 28 | and change detection techniques with simple logic. [Persistent][] data presents
|
---|
| 29 | a mutative API which does not update the data in-place, but instead always
|
---|
| 30 | yields new updated data.
|
---|
| 31 |
|
---|
| 32 | Immutable.js provides many Persistent Immutable data structures including:
|
---|
| 33 | `List`, `Stack`, `Map`, `OrderedMap`, `Set`, `OrderedSet` and `Record`.
|
---|
| 34 |
|
---|
| 35 | These data structures are highly efficient on modern JavaScript VMs by using
|
---|
| 36 | structural sharing via [hash maps tries][] and [vector tries][] as popularized
|
---|
| 37 | by Clojure and Scala, minimizing the need to copy or cache data.
|
---|
| 38 |
|
---|
| 39 | Immutable.js also provides a lazy `Seq`, allowing efficient
|
---|
| 40 | chaining of collection methods like `map` and `filter` without creating
|
---|
| 41 | intermediate representations. Create some `Seq` with `Range` and `Repeat`.
|
---|
| 42 |
|
---|
| 43 | Want to hear more? Watch the presentation about Immutable.js:
|
---|
| 44 |
|
---|
| 45 | [![Immutable Data and React](website/public/Immutable-Data-and-React-YouTube.png)](https://youtu.be/I7IdS-PbEgI)
|
---|
| 46 |
|
---|
| 47 | [README.md]: https://github.com/immutable-js/immutable-js/blob/main/README.md
|
---|
| 48 | [immutable.d.ts]: https://github.com/immutable-js/immutable-js/blob/main/type-definitions/immutable.d.ts
|
---|
| 49 | [wiki]: https://github.com/immutable-js/immutable-js/wiki
|
---|
| 50 | [issue]: https://github.com/immutable-js/immutable-js/issues
|
---|
| 51 | [Persistent]: https://en.wikipedia.org/wiki/Persistent_data_structure
|
---|
| 52 | [Immutable]: https://en.wikipedia.org/wiki/Immutable_object
|
---|
| 53 | [hash maps tries]: https://en.wikipedia.org/wiki/Hash_array_mapped_trie
|
---|
| 54 | [vector tries]: https://hypirion.com/musings/understanding-persistent-vector-pt-1
|
---|
| 55 |
|
---|
| 56 | ## Getting started
|
---|
| 57 |
|
---|
| 58 | Install `immutable` using npm.
|
---|
| 59 |
|
---|
| 60 | ```shell
|
---|
| 61 | # using npm
|
---|
| 62 | npm install immutable
|
---|
| 63 |
|
---|
| 64 | # using Yarn
|
---|
| 65 | yarn add immutable
|
---|
| 66 |
|
---|
| 67 | # using pnpm
|
---|
| 68 | pnpm add immutable
|
---|
| 69 |
|
---|
| 70 | # using Bun
|
---|
| 71 | bun add immutable
|
---|
| 72 | ```
|
---|
| 73 |
|
---|
| 74 | Then require it into any module.
|
---|
| 75 |
|
---|
| 76 | <!-- runkit:activate -->
|
---|
| 77 |
|
---|
| 78 | ```js
|
---|
| 79 | const { Map } = require('immutable');
|
---|
| 80 | const map1 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 81 | const map2 = map1.set('b', 50);
|
---|
| 82 | map1.get('b') + ' vs. ' + map2.get('b'); // 2 vs. 50
|
---|
| 83 | ```
|
---|
| 84 |
|
---|
| 85 | ### Browser
|
---|
| 86 |
|
---|
| 87 | Immutable.js has no dependencies, which makes it predictable to include in a Browser.
|
---|
| 88 |
|
---|
| 89 | It's highly recommended to use a module bundler like [webpack](https://webpack.js.org/),
|
---|
| 90 | [rollup](https://rollupjs.org/), or
|
---|
| 91 | [browserify](https://browserify.org/). The `immutable` npm module works
|
---|
| 92 | without any additional consideration. All examples throughout the documentation
|
---|
| 93 | will assume use of this kind of tool.
|
---|
| 94 |
|
---|
| 95 | Alternatively, Immutable.js may be directly included as a script tag. Download
|
---|
| 96 | or link to a CDN such as [CDNJS](https://cdnjs.com/libraries/immutable)
|
---|
| 97 | or [jsDelivr](https://www.jsdelivr.com/package/npm/immutable).
|
---|
| 98 |
|
---|
| 99 | Use a script tag to directly add `Immutable` to the global scope:
|
---|
| 100 |
|
---|
| 101 | ```html
|
---|
| 102 | <script src="immutable.min.js"></script>
|
---|
| 103 | <script>
|
---|
| 104 | var map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
|
---|
| 105 | var map2 = map1.set('b', 50);
|
---|
| 106 | map1.get('b'); // 2
|
---|
| 107 | map2.get('b'); // 50
|
---|
| 108 | </script>
|
---|
| 109 | ```
|
---|
| 110 |
|
---|
| 111 | Or use an AMD-style loader (such as [RequireJS](https://requirejs.org/)):
|
---|
| 112 |
|
---|
| 113 | ```js
|
---|
| 114 | require(['./immutable.min.js'], function (Immutable) {
|
---|
| 115 | var map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
|
---|
| 116 | var map2 = map1.set('b', 50);
|
---|
| 117 | map1.get('b'); // 2
|
---|
| 118 | map2.get('b'); // 50
|
---|
| 119 | });
|
---|
| 120 | ```
|
---|
| 121 |
|
---|
| 122 | ### Flow & TypeScript
|
---|
| 123 |
|
---|
| 124 | Use these Immutable collections and sequences as you would use native
|
---|
| 125 | collections in your [Flowtype](https://flowtype.org/) or [TypeScript](https://typescriptlang.org) programs while still taking
|
---|
| 126 | advantage of type generics, error detection, and auto-complete in your IDE.
|
---|
| 127 |
|
---|
| 128 | Installing `immutable` via npm brings with it type definitions for Flow (v0.55.0 or higher)
|
---|
| 129 | and TypeScript (v2.1.0 or higher), so you shouldn't need to do anything at all!
|
---|
| 130 |
|
---|
| 131 | #### Using TypeScript with Immutable.js v4
|
---|
| 132 |
|
---|
| 133 | Immutable.js type definitions embrace ES2015. While Immutable.js itself supports
|
---|
| 134 | legacy browsers and environments, its type definitions require TypeScript's 2015
|
---|
| 135 | lib. Include either `"target": "es2015"` or `"lib": "es2015"` in your
|
---|
| 136 | `tsconfig.json`, or provide `--target es2015` or `--lib es2015` to the
|
---|
| 137 | `tsc` command.
|
---|
| 138 |
|
---|
| 139 | <!-- runkit:activate -->
|
---|
| 140 |
|
---|
| 141 | ```js
|
---|
| 142 | const { Map } = require('immutable');
|
---|
| 143 | const map1 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 144 | const map2 = map1.set('b', 50);
|
---|
| 145 | map1.get('b') + ' vs. ' + map2.get('b'); // 2 vs. 50
|
---|
| 146 | ```
|
---|
| 147 |
|
---|
| 148 | #### Using TypeScript with Immutable.js v3 and earlier:
|
---|
| 149 |
|
---|
| 150 | Previous versions of Immutable.js include a reference file which you can include
|
---|
| 151 | via relative path to the type definitions at the top of your file.
|
---|
| 152 |
|
---|
| 153 | ```js
|
---|
| 154 | ///<reference path='./node_modules/immutable/dist/immutable.d.ts'/>
|
---|
| 155 | import { Map } from 'immutable';
|
---|
| 156 | var map1: Map<string, number>;
|
---|
| 157 | map1 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 158 | var map2 = map1.set('b', 50);
|
---|
| 159 | map1.get('b'); // 2
|
---|
| 160 | map2.get('b'); // 50
|
---|
| 161 | ```
|
---|
| 162 |
|
---|
| 163 | ## The case for Immutability
|
---|
| 164 |
|
---|
| 165 | Much of what makes application development difficult is tracking mutation and
|
---|
| 166 | maintaining state. Developing with immutable data encourages you to think
|
---|
| 167 | differently about how data flows through your application.
|
---|
| 168 |
|
---|
| 169 | Subscribing to data events throughout your application creates a huge overhead of
|
---|
| 170 | book-keeping which can hurt performance, sometimes dramatically, and creates
|
---|
| 171 | opportunities for areas of your application to get out of sync with each other
|
---|
| 172 | due to easy to make programmer error. Since immutable data never changes,
|
---|
| 173 | subscribing to changes throughout the model is a dead-end and new data can only
|
---|
| 174 | ever be passed from above.
|
---|
| 175 |
|
---|
| 176 | This model of data flow aligns well with the architecture of [React][]
|
---|
| 177 | and especially well with an application designed using the ideas of [Flux][].
|
---|
| 178 |
|
---|
| 179 | When data is passed from above rather than being subscribed to, and you're only
|
---|
| 180 | interested in doing work when something has changed, you can use equality.
|
---|
| 181 |
|
---|
| 182 | Immutable collections should be treated as _values_ rather than _objects_. While
|
---|
| 183 | objects represent some thing which could change over time, a value represents
|
---|
| 184 | the state of that thing at a particular instance of time. This principle is most
|
---|
| 185 | important to understanding the appropriate use of immutable data. In order to
|
---|
| 186 | treat Immutable.js collections as values, it's important to use the
|
---|
| 187 | `Immutable.is()` function or `.equals()` method to determine _value equality_
|
---|
| 188 | instead of the `===` operator which determines object _reference identity_.
|
---|
| 189 |
|
---|
| 190 | <!-- runkit:activate -->
|
---|
| 191 |
|
---|
| 192 | ```js
|
---|
| 193 | const { Map } = require('immutable');
|
---|
| 194 | const map1 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 195 | const map2 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 196 | map1.equals(map2); // true
|
---|
| 197 | map1 === map2; // false
|
---|
| 198 | ```
|
---|
| 199 |
|
---|
| 200 | Note: As a performance optimization Immutable.js attempts to return the existing
|
---|
| 201 | collection when an operation would result in an identical collection, allowing
|
---|
| 202 | for using `===` reference equality to determine if something definitely has not
|
---|
| 203 | changed. This can be extremely useful when used within a memoization function
|
---|
| 204 | which would prefer to re-run the function if a deeper equality check could
|
---|
| 205 | potentially be more costly. The `===` equality check is also used internally by
|
---|
| 206 | `Immutable.is` and `.equals()` as a performance optimization.
|
---|
| 207 |
|
---|
| 208 | <!-- runkit:activate -->
|
---|
| 209 |
|
---|
| 210 | ```js
|
---|
| 211 | const { Map } = require('immutable');
|
---|
| 212 | const map1 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 213 | const map2 = map1.set('b', 2); // Set to same value
|
---|
| 214 | map1 === map2; // true
|
---|
| 215 | ```
|
---|
| 216 |
|
---|
| 217 | If an object is immutable, it can be "copied" simply by making another reference
|
---|
| 218 | to it instead of copying the entire object. Because a reference is much smaller
|
---|
| 219 | than the object itself, this results in memory savings and a potential boost in
|
---|
| 220 | execution speed for programs which rely on copies (such as an undo-stack).
|
---|
| 221 |
|
---|
| 222 | <!-- runkit:activate -->
|
---|
| 223 |
|
---|
| 224 | ```js
|
---|
| 225 | const { Map } = require('immutable');
|
---|
| 226 | const map = Map({ a: 1, b: 2, c: 3 });
|
---|
| 227 | const mapCopy = map; // Look, "copies" are free!
|
---|
| 228 | ```
|
---|
| 229 |
|
---|
| 230 | [React]: https://reactjs.org/
|
---|
| 231 | [Flux]: https://facebook.github.io/flux/docs/in-depth-overview/
|
---|
| 232 |
|
---|
| 233 |
|
---|
| 234 | ## JavaScript-first API
|
---|
| 235 |
|
---|
| 236 | While Immutable.js is inspired by Clojure, Scala, Haskell and other functional
|
---|
| 237 | programming environments, it's designed to bring these powerful concepts to
|
---|
| 238 | JavaScript, and therefore has an Object-Oriented API that closely mirrors that
|
---|
| 239 | of [ES2015][] [Array][], [Map][], and [Set][].
|
---|
| 240 |
|
---|
| 241 | [es2015]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla
|
---|
| 242 | [array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
|
---|
| 243 | [map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
---|
| 244 | [set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
|
---|
| 245 |
|
---|
| 246 | The difference for the immutable collections is that methods which would mutate
|
---|
| 247 | the collection, like `push`, `set`, `unshift` or `splice`, instead return a new
|
---|
| 248 | immutable collection. Methods which return new arrays, like `slice` or `concat`,
|
---|
| 249 | instead return new immutable collections.
|
---|
| 250 |
|
---|
| 251 | <!-- runkit:activate -->
|
---|
| 252 |
|
---|
| 253 | ```js
|
---|
| 254 | const { List } = require('immutable');
|
---|
| 255 | const list1 = List([1, 2]);
|
---|
| 256 | const list2 = list1.push(3, 4, 5);
|
---|
| 257 | const list3 = list2.unshift(0);
|
---|
| 258 | const list4 = list1.concat(list2, list3);
|
---|
| 259 | assert.equal(list1.size, 2);
|
---|
| 260 | assert.equal(list2.size, 5);
|
---|
| 261 | assert.equal(list3.size, 6);
|
---|
| 262 | assert.equal(list4.size, 13);
|
---|
| 263 | assert.equal(list4.get(0), 1);
|
---|
| 264 | ```
|
---|
| 265 |
|
---|
| 266 | Almost all of the methods on [Array][] will be found in similar form on
|
---|
| 267 | `Immutable.List`, those of [Map][] found on `Immutable.Map`, and those of [Set][]
|
---|
| 268 | found on `Immutable.Set`, including collection operations like `forEach()`
|
---|
| 269 | and `map()`.
|
---|
| 270 |
|
---|
| 271 | <!-- runkit:activate -->
|
---|
| 272 |
|
---|
| 273 | ```js
|
---|
| 274 | const { Map } = require('immutable');
|
---|
| 275 | const alpha = Map({ a: 1, b: 2, c: 3, d: 4 });
|
---|
| 276 | alpha.map((v, k) => k.toUpperCase()).join();
|
---|
| 277 | // 'A,B,C,D'
|
---|
| 278 | ```
|
---|
| 279 |
|
---|
| 280 | ### Convert from raw JavaScript objects and arrays.
|
---|
| 281 |
|
---|
| 282 | Designed to inter-operate with your existing JavaScript, Immutable.js
|
---|
| 283 | accepts plain JavaScript Arrays and Objects anywhere a method expects a
|
---|
| 284 | `Collection`.
|
---|
| 285 |
|
---|
| 286 | <!-- runkit:activate -->
|
---|
| 287 |
|
---|
| 288 | ```js
|
---|
| 289 | const { Map, List } = require('immutable');
|
---|
| 290 | const map1 = Map({ a: 1, b: 2, c: 3, d: 4 });
|
---|
| 291 | const map2 = Map({ c: 10, a: 20, t: 30 });
|
---|
| 292 | const obj = { d: 100, o: 200, g: 300 };
|
---|
| 293 | const map3 = map1.merge(map2, obj);
|
---|
| 294 | // Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }
|
---|
| 295 | const list1 = List([1, 2, 3]);
|
---|
| 296 | const list2 = List([4, 5, 6]);
|
---|
| 297 | const array = [7, 8, 9];
|
---|
| 298 | const list3 = list1.concat(list2, array);
|
---|
| 299 | // List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
|
---|
| 300 | ```
|
---|
| 301 |
|
---|
| 302 | This is possible because Immutable.js can treat any JavaScript Array or Object
|
---|
| 303 | as a Collection. You can take advantage of this in order to get sophisticated
|
---|
| 304 | collection methods on JavaScript Objects, which otherwise have a very sparse
|
---|
| 305 | native API. Because Seq evaluates lazily and does not cache intermediate
|
---|
| 306 | results, these operations can be extremely efficient.
|
---|
| 307 |
|
---|
| 308 | <!-- runkit:activate -->
|
---|
| 309 |
|
---|
| 310 | ```js
|
---|
| 311 | const { Seq } = require('immutable');
|
---|
| 312 | const myObject = { a: 1, b: 2, c: 3 };
|
---|
| 313 | Seq(myObject)
|
---|
| 314 | .map(x => x * x)
|
---|
| 315 | .toObject();
|
---|
| 316 | // { a: 1, b: 4, c: 9 }
|
---|
| 317 | ```
|
---|
| 318 |
|
---|
| 319 | Keep in mind, when using JS objects to construct Immutable Maps, that
|
---|
| 320 | JavaScript Object properties are always strings, even if written in a quote-less
|
---|
| 321 | shorthand, while Immutable Maps accept keys of any type.
|
---|
| 322 |
|
---|
| 323 | <!-- runkit:activate -->
|
---|
| 324 |
|
---|
| 325 | ```js
|
---|
| 326 | const { fromJS } = require('immutable');
|
---|
| 327 |
|
---|
| 328 | const obj = { 1: 'one' };
|
---|
| 329 | console.log(Object.keys(obj)); // [ "1" ]
|
---|
| 330 | console.log(obj['1'], obj[1]); // "one", "one"
|
---|
| 331 |
|
---|
| 332 | const map = fromJS(obj);
|
---|
| 333 | console.log(map.get('1'), map.get(1)); // "one", undefined
|
---|
| 334 | ```
|
---|
| 335 |
|
---|
| 336 | Property access for JavaScript Objects first converts the key to a string, but
|
---|
| 337 | since Immutable Map keys can be of any type the argument to `get()` is
|
---|
| 338 | not altered.
|
---|
| 339 |
|
---|
| 340 | ### Converts back to raw JavaScript objects.
|
---|
| 341 |
|
---|
| 342 | All Immutable.js Collections can be converted to plain JavaScript Arrays and
|
---|
| 343 | Objects shallowly with `toArray()` and `toObject()` or deeply with `toJS()`.
|
---|
| 344 | All Immutable Collections also implement `toJSON()` allowing them to be passed
|
---|
| 345 | to `JSON.stringify` directly. They also respect the custom `toJSON()` methods of
|
---|
| 346 | nested objects.
|
---|
| 347 |
|
---|
| 348 | <!-- runkit:activate -->
|
---|
| 349 |
|
---|
| 350 | ```js
|
---|
| 351 | const { Map, List } = require('immutable');
|
---|
| 352 | const deep = Map({ a: 1, b: 2, c: List([3, 4, 5]) });
|
---|
| 353 | console.log(deep.toObject()); // { a: 1, b: 2, c: List [ 3, 4, 5 ] }
|
---|
| 354 | console.log(deep.toArray()); // [ 1, 2, List [ 3, 4, 5 ] ]
|
---|
| 355 | console.log(deep.toJS()); // { a: 1, b: 2, c: [ 3, 4, 5 ] }
|
---|
| 356 | JSON.stringify(deep); // '{"a":1,"b":2,"c":[3,4,5]}'
|
---|
| 357 | ```
|
---|
| 358 |
|
---|
| 359 | ### Embraces ES2015
|
---|
| 360 |
|
---|
| 361 | Immutable.js supports all JavaScript environments, including legacy
|
---|
| 362 | browsers (even IE11). However it also takes advantage of features added to
|
---|
| 363 | JavaScript in [ES2015][], the latest standard version of JavaScript, including
|
---|
| 364 | [Iterators][], [Arrow Functions][], [Classes][], and [Modules][]. It's inspired
|
---|
| 365 | by the native [Map][] and [Set][] collections added to ES2015.
|
---|
| 366 |
|
---|
| 367 | All examples in the Documentation are presented in ES2015. To run in all
|
---|
| 368 | browsers, they need to be translated to ES5.
|
---|
| 369 |
|
---|
| 370 | ```js
|
---|
| 371 | // ES2015
|
---|
| 372 | const mapped = foo.map(x => x * x);
|
---|
| 373 | // ES5
|
---|
| 374 | var mapped = foo.map(function (x) {
|
---|
| 375 | return x * x;
|
---|
| 376 | });
|
---|
| 377 | ```
|
---|
| 378 |
|
---|
| 379 | All Immutable.js collections are [Iterable][iterators], which allows them to be
|
---|
| 380 | used anywhere an Iterable is expected, such as when spreading into an Array.
|
---|
| 381 |
|
---|
| 382 | <!-- runkit:activate -->
|
---|
| 383 |
|
---|
| 384 | ```js
|
---|
| 385 | const { List } = require('immutable');
|
---|
| 386 | const aList = List([1, 2, 3]);
|
---|
| 387 | const anArray = [0, ...aList, 4, 5]; // [ 0, 1, 2, 3, 4, 5 ]
|
---|
| 388 | ```
|
---|
| 389 |
|
---|
| 390 | Note: A Collection is always iterated in the same order, however that order may
|
---|
| 391 | not always be well defined, as is the case for the `Map` and `Set`.
|
---|
| 392 |
|
---|
| 393 | [Iterators]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/The_Iterator_protocol
|
---|
| 394 | [Arrow Functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
|
---|
| 395 | [Classes]: https://wiki.ecmascript.org/doku.php?id=strawman:maximally_minimal_classes
|
---|
| 396 | [Modules]: https://www.2ality.com/2014/09/es6-modules-final.html
|
---|
| 397 |
|
---|
| 398 |
|
---|
| 399 | ## Nested Structures
|
---|
| 400 |
|
---|
| 401 | The collections in Immutable.js are intended to be nested, allowing for deep
|
---|
| 402 | trees of data, similar to JSON.
|
---|
| 403 |
|
---|
| 404 | <!-- runkit:activate -->
|
---|
| 405 |
|
---|
| 406 | ```js
|
---|
| 407 | const { fromJS } = require('immutable');
|
---|
| 408 | const nested = fromJS({ a: { b: { c: [3, 4, 5] } } });
|
---|
| 409 | // Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
|
---|
| 410 | ```
|
---|
| 411 |
|
---|
| 412 | A few power-tools allow for reading and operating on nested data. The
|
---|
| 413 | most useful are `mergeDeep`, `getIn`, `setIn`, and `updateIn`, found on `List`,
|
---|
| 414 | `Map` and `OrderedMap`.
|
---|
| 415 |
|
---|
| 416 | <!-- runkit:activate -->
|
---|
| 417 |
|
---|
| 418 | ```js
|
---|
| 419 | const { fromJS } = require('immutable');
|
---|
| 420 | const nested = fromJS({ a: { b: { c: [3, 4, 5] } } });
|
---|
| 421 |
|
---|
| 422 | const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } });
|
---|
| 423 | // Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
|
---|
| 424 |
|
---|
| 425 | console.log(nested2.getIn(['a', 'b', 'd'])); // 6
|
---|
| 426 |
|
---|
| 427 | const nested3 = nested2.updateIn(['a', 'b', 'd'], value => value + 1);
|
---|
| 428 | console.log(nested3);
|
---|
| 429 | // Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }
|
---|
| 430 |
|
---|
| 431 | const nested4 = nested3.updateIn(['a', 'b', 'c'], list => list.push(6));
|
---|
| 432 | // Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
|
---|
| 433 | ```
|
---|
| 434 |
|
---|
| 435 | ## Equality treats Collections as Values
|
---|
| 436 |
|
---|
| 437 | Immutable.js collections are treated as pure data _values_. Two immutable
|
---|
| 438 | collections are considered _value equal_ (via `.equals()` or `is()`) if they
|
---|
| 439 | represent the same collection of values. This differs from JavaScript's typical
|
---|
| 440 | _reference equal_ (via `===` or `==`) for Objects and Arrays which only
|
---|
| 441 | determines if two variables represent references to the same object instance.
|
---|
| 442 |
|
---|
| 443 | Consider the example below where two identical `Map` instances are not
|
---|
| 444 | _reference equal_ but are _value equal_.
|
---|
| 445 |
|
---|
| 446 | <!-- runkit:activate -->
|
---|
| 447 |
|
---|
| 448 | ```js
|
---|
| 449 | // First consider:
|
---|
| 450 | const obj1 = { a: 1, b: 2, c: 3 };
|
---|
| 451 | const obj2 = { a: 1, b: 2, c: 3 };
|
---|
| 452 | obj1 !== obj2; // two different instances are always not equal with ===
|
---|
| 453 |
|
---|
| 454 | const { Map, is } = require('immutable');
|
---|
| 455 | const map1 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 456 | const map2 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 457 | map1 !== map2; // two different instances are not reference-equal
|
---|
| 458 | map1.equals(map2); // but are value-equal if they have the same values
|
---|
| 459 | is(map1, map2); // alternatively can use the is() function
|
---|
| 460 | ```
|
---|
| 461 |
|
---|
| 462 | Value equality allows Immutable.js collections to be used as keys in Maps or
|
---|
| 463 | values in Sets, and retrieved with different but equivalent collections:
|
---|
| 464 |
|
---|
| 465 | <!-- runkit:activate -->
|
---|
| 466 |
|
---|
| 467 | ```js
|
---|
| 468 | const { Map, Set } = require('immutable');
|
---|
| 469 | const map1 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 470 | const map2 = Map({ a: 1, b: 2, c: 3 });
|
---|
| 471 | const set = Set().add(map1);
|
---|
| 472 | set.has(map2); // true because these are value-equal
|
---|
| 473 | ```
|
---|
| 474 |
|
---|
| 475 | Note: `is()` uses the same measure of equality as [Object.is][] for scalar
|
---|
| 476 | strings and numbers, but uses value equality for Immutable collections,
|
---|
| 477 | determining if both are immutable and all keys and values are equal
|
---|
| 478 | using the same measure of equality.
|
---|
| 479 |
|
---|
| 480 | [object.is]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
---|
| 481 |
|
---|
| 482 | #### Performance tradeoffs
|
---|
| 483 |
|
---|
| 484 | While value equality is useful in many circumstances, it has different
|
---|
| 485 | performance characteristics than reference equality. Understanding these
|
---|
| 486 | tradeoffs may help you decide which to use in each case, especially when used
|
---|
| 487 | to memoize some operation.
|
---|
| 488 |
|
---|
| 489 | When comparing two collections, value equality may require considering every
|
---|
| 490 | item in each collection, on an `O(N)` time complexity. For large collections of
|
---|
| 491 | values, this could become a costly operation. Though if the two are not equal
|
---|
| 492 | and hardly similar, the inequality is determined very quickly. In contrast, when
|
---|
| 493 | comparing two collections with reference equality, only the initial references
|
---|
| 494 | to memory need to be compared which is not based on the size of the collections,
|
---|
| 495 | which has an `O(1)` time complexity. Checking reference equality is always very
|
---|
| 496 | fast, however just because two collections are not reference-equal does not rule
|
---|
| 497 | out the possibility that they may be value-equal.
|
---|
| 498 |
|
---|
| 499 | #### Return self on no-op optimization
|
---|
| 500 |
|
---|
| 501 | When possible, Immutable.js avoids creating new objects for updates where no
|
---|
| 502 | change in _value_ occurred, to allow for efficient _reference equality_ checking
|
---|
| 503 | to quickly determine if no change occurred.
|
---|
| 504 |
|
---|
| 505 | <!-- runkit:activate -->
|
---|
| 506 |
|
---|
| 507 | ```js
|
---|
| 508 | const { Map } = require('immutable');
|
---|
| 509 | const originalMap = Map({ a: 1, b: 2, c: 3 });
|
---|
| 510 | const updatedMap = originalMap.set('b', 2);
|
---|
| 511 | updatedMap === originalMap; // No-op .set() returned the original reference.
|
---|
| 512 | ```
|
---|
| 513 |
|
---|
| 514 | However updates which do result in a change will return a new reference. Each
|
---|
| 515 | of these operations occur independently, so two similar updates will not return
|
---|
| 516 | the same reference:
|
---|
| 517 |
|
---|
| 518 | <!-- runkit:activate -->
|
---|
| 519 |
|
---|
| 520 | ```js
|
---|
| 521 | const { Map } = require('immutable');
|
---|
| 522 | const originalMap = Map({ a: 1, b: 2, c: 3 });
|
---|
| 523 | const updatedMap = originalMap.set('b', 1000);
|
---|
| 524 | // New instance, leaving the original immutable.
|
---|
| 525 | updatedMap !== originalMap;
|
---|
| 526 | const anotherUpdatedMap = originalMap.set('b', 1000);
|
---|
| 527 | // Despite both the results of the same operation, each created a new reference.
|
---|
| 528 | anotherUpdatedMap !== updatedMap;
|
---|
| 529 | // However the two are value equal.
|
---|
| 530 | anotherUpdatedMap.equals(updatedMap);
|
---|
| 531 | ```
|
---|
| 532 |
|
---|
| 533 | ## Batching Mutations
|
---|
| 534 |
|
---|
| 535 | > If a tree falls in the woods, does it make a sound?
|
---|
| 536 | >
|
---|
| 537 | > If a pure function mutates some local data in order to produce an immutable
|
---|
| 538 | > return value, is that ok?
|
---|
| 539 | >
|
---|
| 540 | > — Rich Hickey, Clojure
|
---|
| 541 |
|
---|
| 542 | Applying a mutation to create a new immutable object results in some overhead,
|
---|
| 543 | which can add up to a minor performance penalty. If you need to apply a series
|
---|
| 544 | of mutations locally before returning, Immutable.js gives you the ability to
|
---|
| 545 | create a temporary mutable (transient) copy of a collection and apply a batch of
|
---|
| 546 | mutations in a performant manner by using `withMutations`. In fact, this is
|
---|
| 547 | exactly how Immutable.js applies complex mutations itself.
|
---|
| 548 |
|
---|
| 549 | As an example, building `list2` results in the creation of 1, not 3, new
|
---|
| 550 | immutable Lists.
|
---|
| 551 |
|
---|
| 552 | <!-- runkit:activate -->
|
---|
| 553 |
|
---|
| 554 | ```js
|
---|
| 555 | const { List } = require('immutable');
|
---|
| 556 | const list1 = List([1, 2, 3]);
|
---|
| 557 | const list2 = list1.withMutations(function (list) {
|
---|
| 558 | list.push(4).push(5).push(6);
|
---|
| 559 | });
|
---|
| 560 | assert.equal(list1.size, 3);
|
---|
| 561 | assert.equal(list2.size, 6);
|
---|
| 562 | ```
|
---|
| 563 |
|
---|
| 564 | Note: Immutable.js also provides `asMutable` and `asImmutable`, but only
|
---|
| 565 | encourages their use when `withMutations` will not suffice. Use caution to not
|
---|
| 566 | return a mutable copy, which could result in undesired behavior.
|
---|
| 567 |
|
---|
| 568 | _Important!_: Only a select few methods can be used in `withMutations` including
|
---|
| 569 | `set`, `push` and `pop`. These methods can be applied directly against a
|
---|
| 570 | persistent data-structure where other methods like `map`, `filter`, `sort`,
|
---|
| 571 | and `splice` will always return new immutable data-structures and never mutate
|
---|
| 572 | a mutable collection.
|
---|
| 573 |
|
---|
| 574 | ## Lazy Seq
|
---|
| 575 |
|
---|
| 576 | `Seq` describes a lazy operation, allowing them to efficiently chain
|
---|
| 577 | use of all the higher-order collection methods (such as `map` and `filter`)
|
---|
| 578 | by not creating intermediate collections.
|
---|
| 579 |
|
---|
| 580 | **Seq is immutable** — Once a Seq is created, it cannot be
|
---|
| 581 | changed, appended to, rearranged or otherwise modified. Instead, any mutative
|
---|
| 582 | method called on a `Seq` will return a new `Seq`.
|
---|
| 583 |
|
---|
| 584 | **Seq is lazy** — `Seq` does as little work as necessary to respond to any
|
---|
| 585 | method call. Values are often created during iteration, including implicit
|
---|
| 586 | iteration when reducing or converting to a concrete data structure such as
|
---|
| 587 | a `List` or JavaScript `Array`.
|
---|
| 588 |
|
---|
| 589 | For example, the following performs no work, because the resulting
|
---|
| 590 | `Seq`'s values are never iterated:
|
---|
| 591 |
|
---|
| 592 | ```js
|
---|
| 593 | const { Seq } = require('immutable');
|
---|
| 594 | const oddSquares = Seq([1, 2, 3, 4, 5, 6, 7, 8])
|
---|
| 595 | .filter(x => x % 2 !== 0)
|
---|
| 596 | .map(x => x * x);
|
---|
| 597 | ```
|
---|
| 598 |
|
---|
| 599 | Once the `Seq` is used, it performs only the work necessary. In this
|
---|
| 600 | example, no intermediate arrays are ever created, filter is called three
|
---|
| 601 | times, and map is only called once:
|
---|
| 602 |
|
---|
| 603 | ```js
|
---|
| 604 | oddSquares.get(1); // 9
|
---|
| 605 | ```
|
---|
| 606 |
|
---|
| 607 | Any collection can be converted to a lazy Seq with `Seq()`.
|
---|
| 608 |
|
---|
| 609 | <!-- runkit:activate -->
|
---|
| 610 |
|
---|
| 611 | ```js
|
---|
| 612 | const { Map, Seq } = require('immutable');
|
---|
| 613 | const map = Map({ a: 1, b: 2, c: 3 });
|
---|
| 614 | const lazySeq = Seq(map);
|
---|
| 615 | ```
|
---|
| 616 |
|
---|
| 617 | `Seq` allows for the efficient chaining of operations, allowing for the
|
---|
| 618 | expression of logic that can otherwise be very tedious:
|
---|
| 619 |
|
---|
| 620 | ```js
|
---|
| 621 | lazySeq
|
---|
| 622 | .flip()
|
---|
| 623 | .map(key => key.toUpperCase())
|
---|
| 624 | .flip();
|
---|
| 625 | // Seq { A: 1, B: 2, C: 3 }
|
---|
| 626 | ```
|
---|
| 627 |
|
---|
| 628 | As well as expressing logic that would otherwise seem memory or time
|
---|
| 629 | limited, for example `Range` is a special kind of Lazy sequence.
|
---|
| 630 |
|
---|
| 631 | <!-- runkit:activate -->
|
---|
| 632 |
|
---|
| 633 | ```js
|
---|
| 634 | const { Range } = require('immutable');
|
---|
| 635 | Range(1, Infinity)
|
---|
| 636 | .skip(1000)
|
---|
| 637 | .map(n => -n)
|
---|
| 638 | .filter(n => n % 2 === 0)
|
---|
| 639 | .take(2)
|
---|
| 640 | .reduce((r, n) => r * n, 1);
|
---|
| 641 | // 1006008
|
---|
| 642 | ```
|
---|
| 643 |
|
---|
| 644 | ## Comparison of filter(), groupBy(), and partition()
|
---|
| 645 |
|
---|
| 646 | The `filter()`, `groupBy()`, and `partition()` methods are similar in that they
|
---|
| 647 | all divide a collection into parts based on applying a function to each element.
|
---|
| 648 | All three call the predicate or grouping function once for each item in the
|
---|
| 649 | input collection. All three return zero or more collections of the same type as
|
---|
| 650 | their input. The returned collections are always distinct from the input
|
---|
| 651 | (according to `===`), even if the contents are identical.
|
---|
| 652 |
|
---|
| 653 | Of these methods, `filter()` is the only one that is lazy and the only one which
|
---|
| 654 | discards items from the input collection. It is the simplest to use, and the
|
---|
| 655 | fact that it returns exactly one collection makes it easy to combine with other
|
---|
| 656 | methods to form a pipeline of operations.
|
---|
| 657 |
|
---|
| 658 | The `partition()` method is similar to an eager version of `filter()`, but it
|
---|
| 659 | returns two collections; the first contains the items that would have been
|
---|
| 660 | discarded by `filter()`, and the second contains the items that would have been
|
---|
| 661 | kept. It always returns an array of exactly two collections, which can make it
|
---|
| 662 | easier to use than `groupBy()`. Compared to making two separate calls to
|
---|
| 663 | `filter()`, `partition()` makes half as many calls it the predicate passed to
|
---|
| 664 | it.
|
---|
| 665 |
|
---|
| 666 | The `groupBy()` method is a more generalized version of `partition()` that can
|
---|
| 667 | group by an arbitrary function rather than just a predicate. It returns a map
|
---|
| 668 | with zero or more entries, where the keys are the values returned by the
|
---|
| 669 | grouping function, and the values are nonempty collections of the corresponding
|
---|
| 670 | arguments. Although `groupBy()` is more powerful than `partition()`, it can be
|
---|
| 671 | harder to use because it is not always possible predict in advance how many
|
---|
| 672 | entries the returned map will have and what their keys will be.
|
---|
| 673 |
|
---|
| 674 | | Summary | `filter` | `partition` | `groupBy` |
|
---|
| 675 | |:------------------------------|:---------|:------------|:---------------|
|
---|
| 676 | | ease of use | easiest | moderate | hardest |
|
---|
| 677 | | generality | least | moderate | most |
|
---|
| 678 | | laziness | lazy | eager | eager |
|
---|
| 679 | | # of returned sub-collections | 1 | 2 | 0 or more |
|
---|
| 680 | | sub-collections may be empty | yes | yes | no |
|
---|
| 681 | | can discard items | yes | no | no |
|
---|
| 682 | | wrapping container | none | array | Map/OrderedMap |
|
---|
| 683 |
|
---|
| 684 | ## Additional Tools and Resources
|
---|
| 685 |
|
---|
| 686 | - [Atom-store](https://github.com/jameshopkins/atom-store/)
|
---|
| 687 | - A Clojure-inspired atom implementation in Javascript with configurability
|
---|
| 688 | for external persistance.
|
---|
| 689 |
|
---|
| 690 | - [Chai Immutable](https://github.com/astorije/chai-immutable)
|
---|
| 691 | - If you are using the [Chai Assertion Library](https://chaijs.com/), this
|
---|
| 692 | provides a set of assertions to use against Immutable.js collections.
|
---|
| 693 |
|
---|
| 694 | - [Fantasy-land](https://github.com/fantasyland/fantasy-land)
|
---|
| 695 | - Specification for interoperability of common algebraic structures in JavaScript.
|
---|
| 696 |
|
---|
| 697 | - [Immutagen](https://github.com/pelotom/immutagen)
|
---|
| 698 | - A library for simulating immutable generators in JavaScript.
|
---|
| 699 |
|
---|
| 700 | - [Immutable-cursor](https://github.com/redbadger/immutable-cursor)
|
---|
| 701 | - Immutable cursors incorporating the Immutable.js interface over
|
---|
| 702 | Clojure-inspired atom.
|
---|
| 703 |
|
---|
| 704 | - [Immutable-ext](https://github.com/DrBoolean/immutable-ext)
|
---|
| 705 | - Fantasyland extensions for immutablejs
|
---|
| 706 |
|
---|
| 707 | - [Immutable-js-tools](https://github.com/madeinfree/immutable-js-tools)
|
---|
| 708 | - Util tools for immutable.js
|
---|
| 709 |
|
---|
| 710 | - [Immutable-Redux](https://github.com/gajus/redux-immutable)
|
---|
| 711 | - redux-immutable is used to create an equivalent function of Redux
|
---|
| 712 | combineReducers that works with Immutable.js state.
|
---|
| 713 |
|
---|
| 714 | - [Immutable-Treeutils](https://github.com/lukasbuenger/immutable-treeutils)
|
---|
| 715 | - Functional tree traversal helpers for ImmutableJS data structures.
|
---|
| 716 |
|
---|
| 717 | - [Irecord](https://github.com/ericelliott/irecord)
|
---|
| 718 | - An immutable store that exposes an RxJS observable. Great for React.
|
---|
| 719 |
|
---|
| 720 | - [Mudash](https://github.com/brianneisler/mudash)
|
---|
| 721 | - Lodash wrapper providing Immutable.JS support.
|
---|
| 722 |
|
---|
| 723 | - [React-Immutable-PropTypes](https://github.com/HurricaneJames/react-immutable-proptypes)
|
---|
| 724 | - PropType validators that work with Immutable.js.
|
---|
| 725 |
|
---|
| 726 | - [Redux-Immutablejs](https://github.com/indexiatech/redux-immutablejs)
|
---|
| 727 | - Redux Immutable facilities.
|
---|
| 728 |
|
---|
| 729 | - [Rxstate](https://github.com/yamalight/rxstate)
|
---|
| 730 | - Simple opinionated state management library based on RxJS and Immutable.js.
|
---|
| 731 |
|
---|
| 732 | - [Transit-Immutable-js](https://github.com/glenjamin/transit-immutable-js)
|
---|
| 733 | - Transit serialisation for Immutable.js.
|
---|
| 734 | - See also: [Transit-js](https://github.com/cognitect/transit-js)
|
---|
| 735 |
|
---|
| 736 | Have an additional tool designed to work with Immutable.js?
|
---|
| 737 | Submit a PR to add it to this list in alphabetical order.
|
---|
| 738 |
|
---|
| 739 | ## Contributing
|
---|
| 740 |
|
---|
| 741 | Use [Github issues](https://github.com/immutable-js/immutable-js/issues) for requests.
|
---|
| 742 |
|
---|
| 743 | We actively welcome pull requests, learn how to [contribute](https://github.com/immutable-js/immutable-js/blob/main/.github/CONTRIBUTING.md).
|
---|
| 744 |
|
---|
| 745 | Immutable.js is maintained within the [Contributor Covenant's Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/).
|
---|
| 746 |
|
---|
| 747 | ### Changelog
|
---|
| 748 |
|
---|
| 749 | Changes are tracked as [Github releases](https://github.com/immutable-js/immutable-js/releases).
|
---|
| 750 |
|
---|
| 751 | ### License
|
---|
| 752 |
|
---|
| 753 | Immutable.js is [MIT-licensed](./LICENSE).
|
---|
| 754 |
|
---|
| 755 | ### Thanks
|
---|
| 756 |
|
---|
| 757 | [Phil Bagwell](https://www.youtube.com/watch?v=K2NYwP90bNs), for his inspiration
|
---|
| 758 | and research in persistent data structures.
|
---|
| 759 |
|
---|
| 760 | [Hugh Jackson](https://github.com/hughfdjackson/), for providing the npm package
|
---|
| 761 | name. If you're looking for his unsupported package, see [this repository](https://github.com/hughfdjackson/immutable).
|
---|