└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Immutable.js for beginners 2 | 3 | This documentation is trying to help people who start using [immutable.js](https://facebook.github.io/immutable-js/) but at the same time feeling confused by the original overwhelming documentation. 4 | 5 | **Worth Mentioning:** The documentation syntax of [immutable.js](https://facebook.github.io/immutable-js/) is written with [Typescript](http://www.typescriptlang.org/docs/handbook/basic-types.html), so if you are confused, it is not your fault :) 6 | 7 | **If you are looking for basic concepts**, please look at the last section `Resources`, I put up a few links to hook you up with some important concepts in Immutable / Immutablejs. 8 | 9 | 10 | ## Join our conversation for Immutable.js for beginners on [Slack](https://immutablejs4beginners.slack.com/) 11 | 12 | ## Table of Contents 13 | * [Map](#map) 14 | * [List](#list) 15 | * [Other Data Structure](#other-data-structure) (Set, Record, Seq) 16 | * [Resources](#resources) 17 | * [Contributions](#contributions) 18 | 19 | ## Map 20 | 21 | Assume: 22 | 23 | * you have imported your immutablejs somewhere 24 | * and, you have the following data structure 25 | 26 | ```javascript 27 | import { Map } from 'immutable' 28 | 29 | let me = Map({ 30 | name: 'roy', 31 | hobby: 'reading' 32 | }) 33 | ``` 34 | 35 | The following will showcase basic operations for map 36 | 37 | ```javascript 38 | me.name // undefined, you can't reference the properties directly 39 | me.get('name') // roy 40 | 41 | me.set('name', 'iroy2000') // setting values 42 | me.get('name') // roy ( what ??? ) 43 | 44 | // !!! Remember in immutable.js when you "mutate" the data, 45 | // it does not modify the original value, but it will return a new one 46 | // ----------------------------------------------------------------------- 47 | 48 | me = me.set('name', 'iroy2000') // setting values 49 | me.get('name') // iroy2000 ( ah !!! ) 50 | 51 | me = me.update('name', item => item.toUppercase) 52 | me.get('name') // IROY2000 53 | ``` 54 | 55 | The following to show case you can merge with a plain object 56 | 57 | ```javascript 58 | let map2 = Map({ 59 | age: '> 20' 60 | }) 61 | 62 | let mapPlainObject = { 63 | gender: 'male' 64 | } 65 | 66 | // ImmutableJS treat JavaScript Array or Object as an Iterable 67 | // ---------------------------------------------------------------- 68 | me.merge(map2, mapPlainObject) // now the map includes age and gender: { "name": "IROY2000", "hobby": "reading", "age": "> 20", "gender": "male" } 69 | ``` 70 | 71 | **Equality Checking** 72 | 73 | Note that the equality checking is applied to not just Map only but to others as well. 74 | 75 | Assume that you have the following data structure 76 | 77 | ```javascript 78 | import { Map } from 'immutable' 79 | 80 | let config = { 81 | name: 'roy', 82 | hobby: 'reading' 83 | } 84 | 85 | let me1 = Map(config) 86 | 87 | let me2 = Map(config) 88 | 89 | 90 | me1 == me2 // false 91 | me1.equals(me2) // true 92 | 93 | 94 | // If you just want to compare if they have same keys 95 | // ---------------------------------------------------------------- 96 | map2.keySeq().equals(me.keySeq()) 97 | 98 | ``` 99 | 100 | **Note:** If you don't understand what is Iterable, it is a collection of objects that allow you to iterate through ( or loop through ) with functions like `map()`, `filter()` or native js `for ... in`. Here is the official documentation in immutablejs on [Iterable](https://facebook.github.io/immutable-js/docs/#/Iterable), and here is a [StackOverflow](http://stackoverflow.com/questions/18884249/checking-whether-something-is-iterable) link if you need more insight. 101 | 102 | 103 | The following showcase some common cases for a map 104 | 105 | ```javascript 106 | 107 | map1.has(['name']) // true 108 | 109 | map1.map((value, key) => `$value is cool`) 110 | 111 | ``` 112 | 113 | **Dealing with Nested structure** 114 | 115 | Assume you have the following structure for your map 116 | 117 | ```javascript 118 | // The following show case you can create immutable data struture 119 | // directly from plain js object using fromJS helper function 120 | // Just remember "array" -> "List" while "object" --> "Map" 121 | // ---------------------------------------------------------------- 122 | 123 | import { fromJS } from 'immutable' 124 | 125 | let me = fromJS({ 126 | name: 'roy', 127 | profile: { 128 | gender: 'male', 129 | language:'javascript' 130 | } 131 | }) 132 | ``` 133 | 134 | The following will explain how to performing deep values getting / setting 135 | 136 | ```javascript 137 | me = me.setIn(['profile', 'language'], 'awesome javascript') 138 | 139 | // The following two "gets" are the same 140 | // ------------------------------------------ 141 | me.get('profile').get('language') // awesome javascript 142 | me.getIn(['profile', 'language']) // awesome javascript 143 | 144 | me = me.mergeDeep({ 145 | profile : { 146 | hobby: 'reading' 147 | } 148 | }) 149 | 150 | me.getIn(['profile', 'hobby']) // reading 151 | 152 | me = me.updateIn(['profile', 'hobby'], item => .toUpperCase()) 153 | 154 | me.getIn(['profile', 'hobby']) // READING 155 | 156 | // The following will grab the profile data 157 | // and delete the key = "hobby" entry in it 158 | // ------------------------------------------- 159 | me.updateIn(['profile'], profile => profile.delete('hobby')) 160 | 161 | ``` 162 | 163 | **Note** `fromJS` is a convenience function that convert nested Objects and Arrays into immutable `Map` and `List` automatically. So if you are using `fromJS`, **do not** apply or mix `Map` or `List` as you could introduce unintentional bugs. 164 | 165 | 166 | ## List 167 | 168 | #### Assume you have the following data structure 169 | 170 | In immutable.js, most of the data structure shares a lot of the same functionalities, because they are inheriting from the same data structure - [Iterable](https://facebook.github.io/immutable-js/docs/#/Iterable) 171 | 172 | 173 | It supports javascript array-like operations, for example, `pop`, `push`, `concat`, `shift` ... 174 | 175 | ```javascript 176 | import { List } from 'immutable' 177 | 178 | let items = List([1,2,3,4]) 179 | 180 | items = items.push(5) // 1,2,3,4,5 181 | 182 | items = items.pop() // 1,2,3,4 183 | 184 | items = items.concat(5, 6) // 1,2,3,4,5,6 185 | 186 | items = items.shift() // 2,3,4,5,6 187 | 188 | ``` 189 | 190 | 191 | ```javascript 192 | import { fromJS } from 'immutable' 193 | 194 | 195 | // Remember "array" -> "List" while "object" --> "Map" 196 | // ------------------------------------------------------ 197 | 198 | let me = fromJS({ 199 | name: 'roy', 200 | friends: [ 201 | { 202 | name: 'captain america', 203 | properties: { 204 | equipment: 'shield' 205 | } 206 | }, 207 | { 208 | name: 'iron man', 209 | properties: { 210 | equipment: 'armor' 211 | } 212 | }, 213 | { 214 | name: 'thor', 215 | properties: { 216 | equipment: 'hammer' 217 | } 218 | } 219 | ] 220 | }) 221 | 222 | ``` 223 | 224 | ```javascript 225 | me.get('friends').get(0).get('name') // captain america 226 | me.getIn(['friends', 0, 'name']) // captain america 227 | 228 | me.get('friends').get(0).get('properties').get('equipment') // shield 229 | me.getIn(['friends', 0, 'properties', 'equipment']) // shield 230 | 231 | 232 | me = me.setIn(['friends', 0, 'properties', 'equipment'], 'glove'); 233 | 234 | let hulk = { 235 | name: 'hulk', 236 | properties: { 237 | equipment: 'body' 238 | } 239 | } 240 | 241 | // now you have new friends 242 | me = me.update('friends', friends => friends.push(hulk)) 243 | 244 | 245 | let friendYouWantToKnowTheIndex = me.get('friends').findIndex(friend => { 246 | return friend.get('name') === 'captain america' 247 | }) 248 | 249 | me = me.setIn(['friends', friendYouWantToKnowTheIndex, 'equipment'], 'shield') 250 | 251 | // How about shuffling all your friends ? 252 | // A better version would be a custom comparator 253 | // ----------------------------------------------- 254 | list = me.update('friends', 255 | friends => friends.sortBy(() => Math.random()) 256 | ) 257 | ``` 258 | 259 | The following show case some common use case for a List 260 | 261 | ```javascript 262 | let heroSalaryList = Immutable.List([ 263 | {name: 'Thor', salary: 1000}, 264 | {name: 'Iron Man', salary: 500}, 265 | {name: 'Hawkeye', salary: 300} 266 | ]) 267 | 268 | // Iterate through the list and reduce the list to a value 269 | // For example, add all salaries of heroes 270 | // ( and divide by number of heroes ) 271 | // ---------------------------------------------------------- 272 | let averageSalary = heroSalaryList.reduce((total, hero) => { 273 | return total + hero.salary 274 | }, 0) / heroSalaryList.count() 275 | 276 | averageSalary == 600 // true 277 | 278 | // fitler the list whose salary cannot be divided by 500 279 | // ---------------------------------------------------------- 280 | let filteredHeroSalaryList = heroSalaryList.filter( 281 | hero => hero.salary % 500 > 1 282 | ) 283 | 284 | filteredHeroSalaryList.count() // only 1 left 285 | 286 | filteredHeroSalaryList.get(0).get('name') // Hawkeye 287 | 288 | ``` 289 | 290 | ## Other Data Structure 291 | 292 | #### Set 293 | An array ( or iterable type ) of unique elements and it will remove any duplicates. 294 | 295 | ```javascript 296 | import { Set } from 'immutable' 297 | 298 | let set1 = Set([1,1,2,2,3,3]) 299 | 300 | set1.count() // 3 301 | set1.toArray() // 1,2,3 302 | 303 | // You can perform subtract, intersect and union on a set. 304 | // But if your set is objects, in order to do that safely, 305 | // you will have to create variables before creating the set. 306 | 307 | const set2 = Set(['hello', 'world', 'what', 'up']); 308 | 309 | set2.subtract['hello'] // it will remove the value from the set 310 | 311 | // consider the following case, which the set contains objects 312 | const greet1 = { 'hello': 'world' }; 313 | const greet2 = { 'what': 'up' }; 314 | 315 | const greetSet = Set([greet1, greet2]); 316 | 317 | greetSet.subtract([{'hello': 'world'}]); // !!! It won't work !!! 318 | 319 | greetSet.subtract([greet1]); // It will work 320 | 321 | ``` 322 | 323 | #### Record 324 | Record let you create a javascript class that comes with default values. 325 | 326 | ```javascript 327 | import { Record } from 'immutable' 328 | 329 | let Villian = Record({ 330 | skill: 'do very bad thing' 331 | }) 332 | 333 | let joker = new Villian({ 334 | skill: 'playing poker' 335 | }) 336 | 337 | joker.toJSON() // { skill: 'playing poker' } 338 | 339 | // It will fall back to default value 340 | // if you remove it 341 | // ----------------------------------------------- 342 | joker = joker.remove('skill') 343 | 344 | joker.toJSON() // { skill: 'do very bad thing' } 345 | 346 | // Values provided to the constructor not found 347 | // in the Record type will be ignored. 348 | // ----------------------------------------------- 349 | 350 | let batwoman = new Villian({ 351 | hairstyle: 'cool' 352 | }) 353 | 354 | batwoman.toJSON() // { skill: 'do very bad thing' } 355 | 356 | ``` 357 | 358 | #### Seq ( Lazy Operation ) 359 | Lazy operation means that the chain methods ( operations ) are not executed until it is requested. And it will stop the execution when the returned items fulfill the request. 360 | 361 | ```javascript 362 | import { Seq } from 'immutable' 363 | 364 | 365 | // Note: The order of the items are important 366 | // ------------------------------------------------------------------ 367 | var femaleHero = Seq.of( 368 | {name:'thor', gender: 'male'}, 369 | {name:'hulk', gender: 'male'}, 370 | {name:'black widow', gender: 'female'} 371 | ) 372 | .filter(hero => hero.gender == 'female') 373 | .map(hero => `${hero.name} is a ${hero.gender}`) 374 | 375 | 376 | // Only performs as much work as necessary to get the result 377 | // ------------------------------------------------------------------ 378 | femaleHero // Nothing will be called 379 | 380 | // This will iterate all three items until the 381 | // first "true" return. All three hero are iterated 382 | // util we found one "female hero" to return. 383 | // ------------------------------------------------------------------ 384 | femaleHero.get(0) // return 'black widow is a female' 385 | 386 | 387 | // If we change the order of the Seq, the first "true" will 388 | // return and the rest of the operations will not even get called. 389 | // 390 | // For example: 391 | // The request of getting first "female hero" is fulfilled, 392 | // the progrm terminates, which means the next operation of 393 | // `filter()` and `map()` will not even get called. 394 | // ------------------------------------------------------------------ 395 | 396 | {name:'black widow', gender: 'female'}, 397 | {name:'thor', gender: 'male'}, 398 | {name:'hulk', gender: 'male'} 399 | 400 | ``` 401 | 402 | 403 | ## Resources 404 | 405 | #### Readings 406 | * [Introduction to Immutablejs](https://auth0.com/blog/intro-to-immutable-js/) 407 | * [Immutablejs overview](http://www.darul.io/post/2015-07-06_immutablejs-overview) 408 | 409 | #### Toolings 410 | * [Immutable live tutorial](http://blog.klipse.tech/javascript/2016/03/30/immutable.html) 411 | * [immutable-docs](http://brentburg.github.io/immutable-docs/api) 412 | 413 | ## Contributions 414 | **Pull Request are welcomed !!!** Hopefully it will be a **community driven** effort to make other developers' life easier when learning immutable.js 415 | 416 | ***PR*** could include more examples, better introduction, presentations, resources ... etc. 417 | 418 | 419 | --------------------------------------------------------------------------------