immhelper v1.0.52
immhelper
Fast and lightweight library helps you to update js objects without mutating them
Install with npm
npm install immhelper --save
Features
- Extreme fast
- Lightweight
- Provide many powerful mutating actions
- Easy to define custom mutating actions
- Support batch processing per spec
- Support pipe processing per spec
- Support deep updating with target path
- Support sub spec with filter
- Support named mutating actions
- Support typescripts auto complete
- Support proxy for selecting and updating target
- Support low level API to update spec for special cases
- Support conditional update actions: if, unless, switch
Playground
https://p5243pkx6q.codesandbox.io/
Change Logs:
1.0.35: Improve performance. Become fastest package for copying immutable objects.
Benchmarks (Fastest to Slowest)
Normal
immhelper: Total elapsed = 72 ms (read) + 2344 ms (write) = 2416 ms Object.assign: Total elapsed = 104 ms (read) + 2394 ms (write) = 2498 ms immutable-assign: Total elapsed = 82 ms (read) + 3814 ms (write) = 3896 ms immer: Total elapsed = 74 ms (read) + 7490 ms (write) = 7564 ms seamless-immutable: Total elapsed = 103 ms (read) + 60833 ms (write) = 60936 ms immutability-helper: Total elapsed = 84 ms (read) + 65249 ms (write) = 65333 ms update-immutable: Total elapsed = 88 ms (read) + 71726 ms (write) = 71814 ms
With Deep Freeze
Object.assign: Total elapsed = 107 ms (read) + 30407 ms (write) = 30514 ms immhelper: Total elapsed = 96 ms (read) + 33167 ms (write) = 33263 ms immer: Total elapsed = 103 ms (read) + 39337 ms (write) = 39440 ms immutable-assign: Total elapsed = 102 ms (read) + 46764 ms (write) = 46866 ms immutability-helper: Total elapsed = 104 ms (read) + 105779 ms (write) = 105883 ms update-immutable: Total elapsed = 108 ms (read) + 107985 ms (write) = 108093 ms
Summary
1.03x Faster than Object.assign 1.61x Faster than immutable-assign 3.13x Faster than immer 25.22x Faster than seamless-immutable 27.04x Faster than immutability-helper 29.72x Faster than update-immutable
Samples
import {
update,
$push,
$unshift,
$splice,
$assign,
$toggle,
$unset,
$set,
$remove
} from "immhelper";
const original = {
a: {
b: {
c: {
d: {
e: {
f: {}
}
}
}
}
},
arrayPush: [],
objMerge: {
name: "Peter"
},
toggleMe: false,
toggleMyProp: {
done: false,
completed: true
},
removeSecond: [1, 2, 3, 4],
removeAppleAndBanana: ["Orange", "Apple", "Banana"],
unsetMyProp: {
data1: new Date(),
data2: true
},
sqrt: 100,
doubleItems: [1, 2, 3, 4, 5, 6, 7, 8],
swapItems: ["left", "right"],
increaseProps: {
one: 1,
two: 2,
three: 3
},
removeByIndexes: [1, 2, 3, 4],
batchProcessing: {},
pipeProcessing: "hello",
doubleOddNumbers: [1, 2, 3, 4],
parentNode: {
childNode: {}
},
parentNodes: [{ id: 0 }, { id: 1 }],
updateTree: {
text: "root",
children: [
{
text: "child 1",
data: {},
children: [{ text: "child 1.1" }]
},
{
text: "child 2",
data: {},
children: [{ text: "child 2.1" }, { text: "child 2.2" }]
}
]
},
usingIfToUpdate: {
value: 1
},
usingUnlessToUpdate: {
dataLoaded: false
},
usingSwitchToUpdate1: 1,
usingSwitchToUpdate2: {
value: true
},
usingFilter: [1, 2, 3, 4, 5],
unsetWithFilter: {
data1: true,
data2: false,
data3: true,
data4: false
}
};
const specs = {
// you can change separator by using configure({ separator: /pattern/ })
"a.b.c.d.e.f": [$set, 100],
"a.b.c.d.e": [$set, "newProp", 100],
arrayPush: [$push, 1, 2, 3, 4, 5],
objMerge: [$assign, { age: 20 }, { school: "A" }],
// using obj method as modifier
sqrt(x) {
return Math.sqrt(x);
},
// toggle property itself
toggleMe: [$toggle],
// toggle child properties
toggleMyProp: [$toggle, "done", "completed"],
unsetMyProp: [$unset, "data1", "data2"],
removeSecond: [$splice, 1, 1],
// remove array items by its value
removeAppleAndBanana: [$remove, "Apple", "Banana"],
// using sub spec to update all array items
// sub spec syntax [spec]
// spec can be [action, ...args] or spec tree { a: { b: ....} }
doubleItems: [[x => x * 2]],
// use action name instead of function
swapItems: ["swap", 0, 1],
// using sub spec to update all obj values
increaseProps: [[x => x + 1]],
removeByIndexes: ["removeAt", 3, 1],
batchProcessing: ["batch", ["set", "name", "Peter"], ["set", "age", 20]],
pipeProcessing: ["batch", x => x.toUpperCase(), x => x + " WORLD!!!"],
// apply sub spec for only odd numbers
doubleOddNumbers: [[x => x * 2], x => x % 2],
parentNode: {
// remove childNode its self from parentNode
childNode: ["unset"]
},
// remove item at index 1 from parentNodes array
parentNodes: {
1: ["unset"]
},
updateTree: {
// using conditional spec to update all nodes which has text prop, exclude all data nodes
"?": [node => node && node.text, ["set", "done", true]],
// do same thing with pattern matching
"?/text/i": ["set", "deleted", true],
children: {
// using diff spec for each node
"?"(node, prop) {
if (node && node.text) {
return prop % 2 === 0
? ["set", "isEven", true]
: ["set", "isOdd", true];
}
return undefined;
}
}
},
usingIfToUpdate: [
"if",
x => x % 2 === 0,
["set", "isEven", true],
["set", "isOdd", true]
],
usingUnlessToUpdate: [
"unless",
x => x.dataLoaded,
["set", "text", "loading..."]
],
usingSwitchToUpdate1: [
"switch",
{
1: ["set", "one"],
2: ["set", "two"],
default: ["set", "other"]
}
],
usingSwitchToUpdate2: [
"switch",
x => (x.value ? "male" : "female"),
{
male: ["set", "sex", "male"],
default: ["set", "sex", "female"]
}
],
usingFilter: ["filter", x % 2 === 0],
unsetWithFilter: ["unset", (value, key) => !!value]
};
const result = update(original, specs);
expect(result).not.toBe(original);
expect(result).toEqual({
a: {
b: {
c: {
d: {
e: {
f: 100,
newProp: 100
}
}
}
}
},
arrayPush: [1, 2, 3, 4, 5],
objMerge: {
name: "Peter",
age: 20,
school: "A"
},
toggleMe: true,
toggleMyProp: {
done: true,
completed: false
},
unsetMyProp: {},
sqrt: 10,
removeSecond: [1, 3, 4],
removeAppleAndBanana: ["Orange"],
doubleItems: [2, 4, 6, 8, 10, 12, 14, 16],
swapItems: ["right", "left"],
increaseProps: {
one: 2,
two: 3,
three: 4
},
removeByIndexes: [1, 3],
batchProcessing: {
name: "Peter",
age: 20
},
pipeProcessing: "HELLO WORLD!!!",
doubleOddNumbers: [2, 2, 6, 4],
parentNode: {},
parentNodes: [{ id: 0 }],
updateTree: {
text: "root",
children: [
{
text: "child 1",
done: true,
deleted: true,
isEven: true,
data: {},
children: [
{ text: "child 1.1", done: true, deleted: true, isEven: true }
]
},
{
text: "child 2",
done: true,
deleted: true,
isOdd: true,
data: {},
children: [
{ text: "child 2.1", done: true, deleted: true, isEven: true },
{ text: "child 2.2", done: true, deleted: true, isOdd: true }
]
}
]
},
usingIfToUpdate: {
value: 1,
isOdd: true
},
usingUnlessToUpdate: {
dataLoaded: false,
text: "loading..."
},
usingSwitchToUpdate1: "one",
usingSwitchToUpdate2: {
value: true,
sex: "male"
},
usingFilter: [2, 4],
unsetWithFilter: {
data2: false,
data4: false
}
});
Typescript support
immhelper.d.ts
declare namespace ImmHelper {
// tuple [selector, action, ...args]
type UpdateSpec<T> = [(model: T) => any, string | Function, ...any[]];
interface Update {
<T>(model: T, ...specs: UpdateSpec<T>[]): T;
default: ImmHelper.Update;
}
}
declare var updatePath: ImmHelper.Update;
export = updatePath;
Usages
/// <reference path="./immhelper.d.ts"/>
import { updatePath, $push, $set } from "immhelper";
const state = {
a: {
b: {
c: []
}
}
};
const newState1 = updatePath(
state,
[x => x.a.b.c, $push, 1, 2, 3],
[x => x.a.b, $set, "newProp", 100]
);
// this is shorter way but there are some limitations
// 1. Do not try to get prop value from obj, using originalState to get value instead
// 2. Only mutating actions are allowed
// 3. Mutating actions must be defined by using define(actionName, func) or define({ actionName: func })
const newState2 = updatePath(
state,
x => x.a.b.c.push(1, 2, 3),
x => (x.a.b.newProp = 100)
);
console.log(newState1, newState2);
Mutating actions
$push, ...items'push', ...itemsactions.push(target, ...items)actions.$push(target, ...items)
push() all the items in array on the target
$pop'pop'actions.pop(target)actions.$pop(target)
$unshift, ...items'unshift', ...itemsactions.unshift(target, ...items)actions.$unshift(target, ...items)
unshift() all the items in array on the target.
$splice, index, count, ...items'splice', index, count, ...itemsactions.splice(target, index, count, ...items)actions.$splice(target, index, count, ...items)
splice() remove item at specified index and push new items at there.
$remove, ...items'remove', ...itemsactions.remove(target, ...items)actions.$remove(target, ...items)
remove specified items from target array
$removeAt, ...indexes'removeAt', ...indexesactions.removeAt(target, ...indexes)actions.$removeAt(target, ...indexes)
remove items from array by indexes
$set, value'set', valueactions.set(target, value)actions.$pop(target, value)
replace the target entirely.
$set, prop, value'set', prop, valueactions.set(target, prop, value)actions.$set(target, prop, value)
set value for specified prop of the target
$toggle'toggle'actions.toggle(target)actions.$toggle(target)
toggle target's value.
$toggle, ...props'toggle', ...propsactions.toggle(target, ...props)actions.$toggle(target, ...props)
toggle all prop values of the target
$unset, ...props'unset', ...propsactions.unset(target, ...props)actions.$unset(target, ...props)
remove keys from the target object or set undefined for array indexes
$assign, ...objects'assign' ...objectsactions.assign(target ...objects)actions.$assign(target, ...objects)
copy the values of all enumerable own properties from one or more source objects to a target object
define(actionName, actionFunc, disableAutoClone)
define new mutating action, if disableAutoClone = true, original value will be passed instead of cloned value. Set disableAutoClone = true if you want to handle value manually, return original value if no change needed.
define("removeAt", function(originalArray, index) {
// nothing to remove
if (index >= originalArray.length) return originalArray;
const newArray = originalArray.slice();
newArray.splice(index, 1);
return newArray;
}, true);
Special use cases
import { without, compact, zip } from "lodash";
import { define, updatePath } from "immhelper";
define({
"+"(current, value) {
return current + value;
},
"-"(current, value) {
return current - value;
},
without,
compact,
zip
});
const state = { counter: 0 };
updatePath(state, [x => x.counter, "+", 1]);
updatePath(state, [x => x.counter, "-", 1]);
Define custom mutating actions
import { define, $set } from "immhelper";
define({
"=": $set,
addOne(currentValue, ...args) {
return currentValue + 1;
}
});
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago