immutable-assign (iassign.js)
Lightweight immutable helper that supports TypeScript type checking, and allows you to continue working with POJO (Plain Old JavaScript Object).
This library is trying to solve following problems:
- Most immutable JavaScript libraries try to encapsulate the data and provide proprietary APIs to work with the data. They are more verbose than normal JavaScript syntax. E.g., map1.get('b') vs map1.b, nested2.getIn(['a', 'b', 'd']) vs nested2.a.b.d, etc.
- Encapsulated data is no more POJO, therefore cannot be easily used with other libraries, e.g., lodash, underscore, etc.
- Most immutable libraries leak themselves throughout your entire application, however, they should have been encapsulated at the place where updates happen. This is also a pain when you need to change to another immutable library that has its own APIs.
- seamless-immutable address some of above issues when reading the properties, but still use verbose APIs to write properties.
- Immutability Helpers allows us to work with POJO, but it has still introduced some magic keywords, such as $set, $push, etc.
- In addition, we lost TypeScript type checking. E.g., when calling nested2.getIn(['a', 'b', 'd']), TypeScript won't be able to warn me if I changed property 'd' to 'e'.
This library has only one method iassign(), which accept a POJO object and return you a new POJO object with specific property updated. I have added some options to freeze input and output using deep-freeze, which can be used in development to make sure they don't change unintentionally by us or the 3rd party libraries.
Performance
Performance of this library should be comparable to Immutable.js, because read operations will always occur more than write operations. When using this library, all your react components can read object properties directly. E.g., you can use <TextBox value={this.state.userinfo.fullName} /> in your components, instead of <TextBox value={this.state.getIn(["userinfo", "fullName"])} />. I.e., the more read operations you have, the more it will outperform Immutable.js.
##Install with npm
npm install immutable-assign --save
Function Signature
function iassign<TObj, TProp, TContext>(
obj: TObj,
getProp: (obj: TObj, context: TContext) => TProp,
setProp: (prop: TProp) => TProp,
context?: TContext,
option?: IIassignOption): TObj;
interface IIassignOption {
freeze: boolean;
freezeInput: boolean;
freezeOutput: boolean;
}
####Example 1: Update nested property
var iassign = require("immutable-assign");
iassign.freeze = true;
var o1 = { a: { b: { c: [[{ d: 11, e: 12 }], [{ d: 21, e: 22 }]], c2: {} }, b2: {} }, a2: {} };
var o2 = iassign(
o1,
function (o) { return o.a.b.c[0][0]; },
function (ci) { ci.d++; return ci; }
);
expect(o1).toEqual({ a: { b: { c: [[{ d: 11, e: 12 }], [{ d: 21, e: 22 }]], c2: {} }, b2: {} }, a2: {} });
expect(o2.a.b.c[0][0].d).toBe(12);
expect(o2).not.toBe(o1);
expect(o2.a).not.toBe(o1.a);
expect(o2.a.b).not.toBe(o1.a.b);
expect(o2.a.b.c).not.toBe(o1.a.b.c);
expect(o2.a.b.c[0]).not.toBe(o1.a.b.c[0]);
expect(o2.a.b.c[0][0]).not.toBe(o1.a.b.c[0][0]);
expect(o2.a.b.c[0][0].d).not.toBe(o1.a.b.c[0][0].d);
expect(o2.a2).toBe(o1.a2);
expect(o2.a.b2).toBe(o1.a.b2);
expect(o2.a.b.c2).toBe(o1.a.b.c2);
expect(o2.a.b.c[0][0].e).toBe(o1.a.b.c[0][0].e);
expect(o2.a.b.c[1][0]).toBe(o1.a.b.c[1][0]);
####Example 2: Update array
var o1 = { a: { b: { c: [[{ d: 11, e: 12 }], [{ d: 21, e: 22 }]], c2: {} }, b2: {} }, a2: {} };
var o2 = iassign(
o1,
function (o) { return o.a.b.c[1]; },
function (c) { c.push(101); return c; }
);
expect(o1).toEqual({ a: { b: { c: [[{ d: 11, e: 12 }], [{ d: 21, e: 22 }]], c2: {} }, b2: {} }, a2: {} });
expect(o2.a.b.c[1][1]).toBe(101);
expect(o2).not.toBe(o1);
expect(o2.a).not.toBe(o1.a);
expect(o2.a.b).not.toBe(o1.a.b);
expect(o2.a.b.c).not.toBe(o1.a.b.c);
expect(o2.a.b.c[1]).not.toBe(o1.a.b.c[1]);
expect(o2.a2).toBe(o1.a2);
expect(o2.a.b2).toBe(o1.a.b2);
expect(o2.a.b.c2).toBe(o1.a.b.c2);
expect(o2.a.b.c[0]).toBe(o1.a.b.c[0]);
expect(o2.a.b.c[0][0]).toBe(o1.a.b.c[0][0]);
expect(o2.a.b.c[1][0]).toBe(o1.a.b.c[1][0]);
####Example 3: Update nested property, referring to external context.
var o1 = { a: { b: { c: [[{ d: 11, e: 12 }], [{ d: 21, e: 22 }]] } } };
var p1 = { a: 0 };
var o2 = iassign(
o1,
function (o, ctx) { return o.a.b.c[ctx.p1.a][0]; },
function (ci) { ci.d++; return ci; },
{ p1: p1 }
);
####Example 4: Work with 3rd party libraries, e.g., lodash
var iassign = require("immutable-assign");
var _ = require("lodash");
iassign.freeze = true;
var o1 = { a: { b: { c: [1, 2, 3] } } };
iassign.freeze = true;
var o2 = iassign(
o1,
function (o) { return o.a.b.c; },
function (c) {
return _.map(c, function (i) { return i + 1; });
}
);
expect(o1).toEqual({ a: { b: { c: [1, 2, 3] } } });
expect(o2.a.b.c).toEqual([2, 3, 4]);
expect(o2).not.toBe(o1);
expect(o2.a).not.toBe(o1.a);
expect(o2.a.b).not.toBe(o1.a.b);
expect(o2.a.b.c).not.toBe(o1.a.b.c);
expect(o2.a.b.c[0]).not.toBe(o1.a.b.c[0]);
##Constraints
- getProp() must be pure function; I.e., it cannot access anything other than the input parameters. e.g., it must not access "this" or "window" objects. In addition, it must not modify the input parameters. It should only return a property that needs to be updated.
##History
- 1.0.16 - Tested in Node.js and major browsers (IE 11, Chrome 52, Firefox 47, Edge 13, PhantomJS 2)