No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

hace 3 años
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. immutability-helper
  2. ===
  3. [![NPM version][npm-image]][npm-url]
  4. [![Build status][travis-image]][travis-url]
  5. [![Test coverage][coveralls-image]][coveralls-url]
  6. [![Downloads][downloads-image]][downloads-url]
  7. [![Minified size][min-size-image]][bundlephobia-url]
  8. [![Gzip size][gzip-size-image]][bundlephobia-url]
  9. Mutate a copy of data without changing the original source
  10. Setup via NPM
  11. ```sh
  12. npm install immutability-helper --save
  13. ```
  14. This is a drop-in replacement for [`react-addons-update`](https://facebook.github.io/react/docs/update.html):
  15. ```js
  16. // import update from 'react-addons-update';
  17. import update from 'immutability-helper';
  18. const state1 = ['x'];
  19. const state2 = update(state1, {$push: ['y']}); // ['x', 'y']
  20. ```
  21. Note that this module has nothing to do with React. However, since this module
  22. is most commonly used with React, the docs will focus on how it can be used with
  23. React.
  24. ## Overview
  25. React lets you use whatever style of data management you want, including
  26. mutation. However, if you can use immutable data in performance-critical parts
  27. of your application it's easy to implement a fast [`shouldComponentUpdate()`](https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate) method
  28. to significantly speed up your app.
  29. Dealing with immutable data in JavaScript is more difficult than in languages
  30. designed for it, like [Clojure](http://clojure.org/). However, we've provided a
  31. simple immutability helper, `update()`, that makes dealing with this type of
  32. data much easier, *without* fundamentally changing how your data is represented.
  33. You can also take a look at Facebook's
  34. [Immutable.js](https://facebook.github.io/immutable-js/docs/) and React’s
  35. [Using Immutable Data Structures](https://facebook.github.io/react/docs/optimizing-performance.html#using-immutable-data-structures) section for more
  36. detail on Immutable.js.
  37. ### The Main Idea
  38. If you mutate data like this:
  39. ```js
  40. myData.x.y.z = 7;
  41. // or...
  42. myData.a.b.push(9);
  43. ```
  44. You have no way of determining which data has changed since the previous copy
  45. has been overwritten. Instead, you need to create a new copy of `myData` and
  46. change only the parts of it that need to be changed. Then you can compare the
  47. old copy of `myData` with the new one in `shouldComponentUpdate()` using
  48. triple-equals:
  49. ```js
  50. const newData = deepCopy(myData);
  51. newData.x.y.z = 7;
  52. newData.a.b.push(9);
  53. ```
  54. Unfortunately, deep copies are expensive, and sometimes impossible. You can
  55. alleviate this by only copying objects that need to be changed and by reusing
  56. the objects that haven't changed. Unfortunately, in today's JavaScript this can
  57. be cumbersome:
  58. ```js
  59. const newData = Object.assign({}, myData, {
  60. x: Object.assign({}, myData.x, {
  61. y: Object.assign({}, myData.x.y, {z: 7}),
  62. }),
  63. a: Object.assign({}, myData.a, {b: myData.a.b.concat(9)})
  64. });
  65. ```
  66. While this is fairly performant (since it only makes a shallow copy of `log n`
  67. objects and reuses the rest), it's a big pain to write. Look at all the
  68. repetition! This is not only annoying, but also provides a large surface area
  69. for bugs.
  70. ## `update()`
  71. `update()` provides simple syntactic sugar around this pattern to make writing
  72. this code easier. This code becomes:
  73. ```js
  74. import update from 'immutability-helper';
  75. const newData = update(myData, {
  76. x: {y: {z: {$set: 7}}},
  77. a: {b: {$push: [9]}}
  78. });
  79. ```
  80. While the syntax takes a little getting used to (though it's inspired by
  81. [MongoDB's query language](http://docs.mongodb.org/manual/core/crud-introduction/#query)) there's no redundancy, it's statically analyzable and it's not much more typing
  82. than the mutative version.
  83. The `$`-prefixed keys are called *commands*. The data structure they are
  84. "mutating" is called the *target*.
  85. ## Available Commands
  86. * `{$push: array}` `push()` all the items in `array` on the target.
  87. * `{$unshift: array}` `unshift()` all the items in `array` on the target.
  88. * `{$splice: array of arrays}` for each item in `arrays` call `splice()` on
  89. the target with the parameters provided by the item. ***Note:** The items in
  90. the array are applied sequentially, so the order matters. The indices of the
  91. target may change during the operation.*
  92. * `{$set: any}` replace the target entirely.
  93. * `{$toggle: array of strings}` toggles a list of boolean fields from the
  94. target object.
  95. * `{$unset: array of strings}` remove the list of keys in `array` from the
  96. target object.
  97. * `{$merge: object}` merge the keys of `object` with the target.
  98. * `{$apply: function}` passes in the current value to the function and
  99. updates it with the new returned value.
  100. * `{$add: array of objects}` add a value to a `Map` or `Set`. When adding to a
  101. `Set` you pass in an array of objects to add, when adding to a Map, you pass
  102. in `[key, value]` arrays like so:
  103. `update(myMap, {$add: [['foo', 'bar'], ['baz', 'boo']]})`
  104. * `{$remove: array of strings}` remove the list of keys in array from a `Map`
  105. or `Set`.
  106. ### Shorthand `$apply` syntax
  107. Additionally, instead of a command object, you can pass a function, and it will
  108. be treated as if it was a command object with the `$apply` command:
  109. `update({a: 1}, {a: function})`. That example would be equivalent to
  110. `update({a: 1}, {a: {$apply: function}})`.
  111. ### Limitations
  112. :warning: `update` only works for _data properties_, not for _accessor properties_ defined with `Object.defineProperty`. It just does not see the latter, and therefore might create shadowing data properties which could break application logic depending on setter side effects. Therefore `update` should only be used on plain data objects that only contain _data properties_ as descendants.
  113. ## Examples
  114. ### Simple push
  115. ```js
  116. const initialArray = [1, 2, 3];
  117. const newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]
  118. ```
  119. `initialArray` is still `[1, 2, 3]`.
  120. ### Nested collections
  121. ```js
  122. const collection = [1, 2, {a: [12, 17, 15]}];
  123. const newCollection = update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
  124. // => [1, 2, {a: [12, 13, 14, 15]}]
  125. ```
  126. This accesses `collection`'s index `2`, key `a`, and does a splice of one item
  127. starting from index `1` (to remove `17`) while inserting `13` and `14`.
  128. ### Updating a value based on its current one
  129. ```js
  130. const obj = {a: 5, b: 3};
  131. const newObj = update(obj, {b: {$apply: function(x) {return x * 2;}}});
  132. // => {a: 5, b: 6}
  133. // This is equivalent, but gets verbose for deeply nested collections:
  134. const newObj2 = update(obj, {b: {$set: obj.b * 2}});
  135. ```
  136. ### (Shallow) Merge
  137. ```js
  138. const obj = {a: 5, b: 3};
  139. const newObj = update(obj, {$merge: {b: 6, c: 7}}); // => {a: 5, b: 6, c: 7}
  140. ```
  141. ### Computed Property Names
  142. Arrays can be indexed into with runtime variables via the ES2015
  143. [Computed Property Names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names)
  144. feature. An object property name expression may be wrapped in brackets [] which
  145. will be evaluated at runtime to form the final property name.
  146. ```js
  147. const collection = {children: ['zero', 'one', 'two']};
  148. const index = 1;
  149. const newCollection = update(collection, {children: {[index]: {$set: 1}}});
  150. // => {children: ['zero', 1, 'two']}
  151. ```
  152. ### Removing an element from an array
  153. ```js
  154. // Delete at a specific index, no matter what value is in it
  155. update(state, { items: { $splice: [[index, 1]] } });
  156. ```
  157. ### [Autovivification](https://en.wikipedia.org/wiki/Autovivification)
  158. Autovivification is the auto creation of new arrays and objects when needed. In
  159. the context of javascript that would mean something like this
  160. ```js
  161. const state = {}
  162. state.a.b.c = 1; // state would equal { a: { b: { c: 1 } } }
  163. ```
  164. Since javascript doesn't have this "feature", the same applies to
  165. `immutability-helper`. The reason why this is practically impossible in
  166. javascript and by extension `immutability-helper` is the following:
  167. ```js
  168. var state = {}
  169. state.thing[0] = 'foo' // What type should state.thing have? Should it be an object or array?
  170. state.thing2[1] = 'foo2' // What about thing2? This must be an object!
  171. state.thing3 = ['thing3'] // This is regular js, this works without autovivification
  172. state.thing3[1] = 'foo3' // Hmm, notice that state.thing2 is an object, yet this is an array
  173. state.thing2.slice // should be undefined
  174. state.thing2.slice // should be a function
  175. ```
  176. If you need to set something deeply nested and don't want to have to set each
  177. layer down the line, consider using this technique which is shown with a
  178. contrived example:
  179. ```js
  180. var state = {}
  181. var desiredState = {
  182. foo: [
  183. {
  184. bar: ['x', 'y', 'z']
  185. },
  186. ],
  187. };
  188. const state2 = update(state, {
  189. foo: foo =>
  190. update(foo || [], {
  191. 0: fooZero =>
  192. update(fooZero || {}, {
  193. bar: bar => update(bar || [], { $push: ["x", "y", "z"] })
  194. })
  195. })
  196. });
  197. console.log(JSON.stringify(state2) === JSON.stringify(desiredState)) // true
  198. // note that state could have been declared as any of the following and it would still output true:
  199. // var state = { foo: [] }
  200. // var state = { foo: [ {} ] }
  201. // var state = { foo: [ {bar: []} ] }
  202. ```
  203. You can also choose to use the extend functionality to add an `$auto` and
  204. `$autoArray` command:
  205. ```js
  206. import update, { extend } from 'immutability-helper';
  207. extend('$auto', function(value, object) {
  208. return object ?
  209. update(object, value):
  210. update({}, value);
  211. });
  212. extend('$autoArray', function(value, object) {
  213. return object ?
  214. update(object, value):
  215. update([], value);
  216. });
  217. var state = {}
  218. var desiredState = {
  219. foo: [
  220. {
  221. bar: ['x', 'y', 'z']
  222. },
  223. ],
  224. };
  225. var state2 = update(state, {
  226. foo: {$autoArray: {
  227. 0: {$auto: {
  228. bar: {$autoArray: {$push: ['x', 'y', 'z']}}
  229. }}
  230. }}
  231. });
  232. console.log(JSON.stringify(state2) === JSON.stringify(desiredState)) // true
  233. ```
  234. ---
  235. ## Adding your own commands
  236. The main difference this module has with `react-addons-update` is that
  237. you can extend this to give it more functionality:
  238. ```js
  239. import update, { extend } from 'immutability-helper';
  240. extend('$addtax', function(tax, original) {
  241. return original + (tax * original);
  242. });
  243. const state = { price: 123 };
  244. const withTax = update(state, {
  245. price: {$addtax: 0.8},
  246. });
  247. assert(JSON.stringify(withTax) === JSON.stringify({ price: 221.4 }));
  248. ```
  249. Note that `original` in the function above is the original object, so if you
  250. plan making a mutation, you must first shallow clone the object. Another option
  251. is to use `update` to make the change
  252. `return update(original, { foo: {$set: 'bar'} })`
  253. If you don't want to mess around with the globally exported `update` function
  254. you can make a copy and work with that copy:
  255. ```js
  256. import { Context } from 'immutability-helper';
  257. const myContext = new Context();
  258. myContext.extend('$foo', function(value, original) {
  259. return 'foo!';
  260. });
  261. myContext.update(/* args */);
  262. ```
  263. [npm-image]: https://img.shields.io/npm/v/immutability-helper.svg?style=flat-square
  264. [npm-url]: https://npmjs.org/package/immutability-helper
  265. [travis-image]: https://img.shields.io/travis/kolodny/immutability-helper.svg?style=flat-square
  266. [travis-url]: https://travis-ci.org/kolodny/immutability-helper
  267. [coveralls-image]: https://img.shields.io/coveralls/kolodny/immutability-helper.svg?style=flat-square
  268. [coveralls-url]: https://coveralls.io/r/kolodny/immutability-helper
  269. [downloads-image]: http://img.shields.io/npm/dm/immutability-helper.svg?style=flat-square
  270. [downloads-url]: https://npmjs.org/package/immutability-helper
  271. [min-size-image]: https://badgen.net/bundlephobia/min/immutability-helper?label=minified
  272. [gzip-size-image]: https://badgen.net/bundlephobia/minzip/immutability-helper?label=gzip
  273. [bundlephobia-url]: https://bundlephobia.com/result?p=immutability-helper