0.0.6 • Published 4 years ago
imh v0.0.6
imh (Immutable Helper)
The extremely fast immutable helper
Installation
$ npm install --save imh
Benchmarks
Single mutation
Library | Read | Write | Total |
---|---|---|---|
immutable.js (fastest) | 265 | 372 | 637 |
imh | 59 | 635 | 694 |
timm | 43 | 662 | 705 |
immutable-assign | 58 | 806 | 864 |
immhelper | 55 | 1,049 | 1,104 |
seamless-immutable (production) | 56 | 13,630 | 13,686 |
immer | 46 | 18,386 | 18,432 |
update-immutable | 44 | 38,532 | 38,576 |
immutability-helper | 50 | 38,666 | 38,716 |
Multiple mutations
Library | Read | Write | Total |
---|---|---|---|
update-immutable (fastest) | 1 | 97 | 98 |
imh | 1 | 198 | 199 |
immutability-helper | 3 | 263 | 266 |
immhelper | 0 | 303 | 303 |
immutable.js | 170 | 538 | 708 |
immer | 1 | 1,151 | 1,152 |
timm | 2 | 1,710 | 1,712 |
Hence, what I recommend (from top to bottom):
- If you don't need immutability, well... just mutate in peace! I mean, in place
- If you need a complete, well-tested, rock-solid library and don't mind using a non-native API for reads: use ImmutableJS
- If you value using plain arrays/objects above other considerations, use imh
- If your typical use cases involve much more reading than writing, use imh as well
- If you do a lot of writes, updating items in long arrays or attributes in fat objects, use ImmutableJS
Usage
import imh from "imh";
let state = {
todos: [{ id: 1, title: "Todo 1", completed: false }],
stats: {
all: 1,
active: 1,
completed: 0,
},
};
const StatsMutation = (current) =>
// mutate stats prop
imh.prop("stats", {
all: current.todos.length,
// compute number of active todos
active: current.todos.filter((todo) => !todo.completed).length,
// compute number of completed todos
completed: current.todos.filter((todo) => todo.completed).length,
});
function AddTodo(id, title) {
state = imh(state, [
// push new item to todos array
imh.prop("todos", imh.push({ id, title, completed: true })),
// update stats
StatsMutation,
]);
}
function ToggleTodo(id) {
state = imh(state, [
// perform toggle action
imh.prop(
// nested prop path
[
// todos prop
"todos",
// toggle item which has id equal to given id
(todo) => todo.id === id,
// completed prop
"completed",
],
// toggle boolean value: true => false, false => true
imh.toggle()
// we can pass arrow function to mutate value as well
// completed => !completed
),
// update stats
StatsMutation,
]);
}
AddTodo(2, "Todo 2");
console.log(state);
/*
{
todos: [
{ id: 1, title: 'Todo 1', completed: false },
{ id: 2, title: 'Todo 2', completed: false },
],
stats: { all: 2, active: 2, completed: 0 }
}
*/
AddTodo(3, "Todo 3");
console.log(state);
/*
{
todos: [
{ id: 1, title: 'Todo 1', completed: false },
{ id: 2, title: 'Todo 2', completed: false },
{ id: 3, title: 'Todo 3', completed: false }
],
stats: { all: 3, active: 3, completed: 0 }
}
*/
ToggleTodo(3);
console.log(state);
/*
{
todos: [
{ id: 1, title: 'Todo 1', completed: false },
{ id: 2, title: 'Todo 2', completed: false },
{ id: 3, title: 'Todo 3', completed: true }
],
stats: { all: 3, active: 2, completed: 1 }
}
*/
API References
imh(obj, mutation/mutations)
imh(1, imh.add(1));
// => 2
// using val() to tell imh that is literal value (not mutation)
imh({ username: "admin", password: "admin" }, [
imh.prop("password", imh.val("123456")),
imh.prop("updatedOn", imh.val(Date.now())),
]);
// using custom mutation (a pure function that returns new value)
imh({ username: "admin", password: "admin" }, [
imh.prop("password", () => "123456"),
imh.prop("updatedOn", () => Date.now()),
]);
// using set() to update object property
imh({ username: "admin", password: "admin" }, [
imh.set("password", "123456"),
imh.set("updatedOn", Date.now()),
]);
imh(mutation/mutations)
Create imh wrapper function
const AddTen = imh(imh.add(10));
AddTen(1);
// => 11
Array
push(...items)
imh([1, 2, 3], imh.push(4, 5, 6));
// => [1, 2, 3, 4, 5, 6]
map(mutation/mutations)
const todos = [
{ id: 1, title: "Todo 1" },
{ id: 2, title: "Todo 2" },
];
imh(
todos,
imh.map((todo) => ({ ...todo, title: todo.title.toUpperCase() }))
);
// => [ { id: 1, title: "TODO 1" }, { id: 2, title: "TODO 2" } ]
imh(todos, imh.map(imh.prop("title", imh.lower())));
// => [ { id: 1, title: "todo 1" }, { id: 2, title: "todo 2" } ]
splice(index, length, ...newItems)
imh([1, 2, 3, 4, 5], imh.splice(2, 2));
// => [1, 2, 5]
imh([1, 2, 3, 4, 5], imh.splice(2, 2, 9, 10));
// => [1, 2, 9, 10, 5]
filter(predicate)
imh(
[1, 2, 3, 4, 5],
imh.filter((number) => number % 2 === 0)
);
// => [2, 4]
sort(compareFn)
imh([3, 2, 1], imh.sort());
// => [1, 2, 3]
imh(
[{ name: "banana" }, { name: "apple" }, { name: "watermelon" }],
imh.sort((a, b) => (a.name > b.name ? 1 : a.name < b.name ? -1 : 0))
);
// => [{ name: "apple" }, { name: "banana" }, { name: "watermelon" }]
orderBy([selector, direction])
// order by name ascending
imh(
[{ name: "banana" }, { name: "apple" }, { name: "watermelon" }],
imh.orderBy((item) => item.name)
);
// => [{ name: "apple" }, { name: "banana" }, { name: "watermelon" }]
// order by name descending
imh(
[{ name: "banana" }, { name: "apple" }, { name: "watermelon" }],
imh.orderBy((item) => item.name, -1)
);
// => [{ name: "watermelon" }, { name: "banana" }, { name: "apple" }]
swap(from, to)
imh([1, 2, 3], imh.swap(0, 2));
// => [3, 2, 1]
remove(...indices)
imh([1, 2, 3], imh.remove(0, 2));
// => [2]
clear()
imh([1, 2, 3], imh.clear());
// => []
pop()
imh([1, 2, 3], imh.pop());
// => [1, 2]
shift()
imh([1, 2, 3], imh.shift());
// => [2, 3]
unshift(...items)
imh([1, 2, 3], imh.unshift(-1, 0));
// => [-1, 0, 1, 2, 3]
reverse()
imh([1, 2, 3], imh.reverse());
// => [3, 2, 1]
Object
prop(name, mutation) & val(value)
Update current / nested object property
const model = { l1: { l2: { l3: { l4: 1 } } } };
imh(
model,
imh.prop("l1", imh.prop("l2", imh.prop("l3", imh.prop("l4", imh.val(2)))))
);
imh(
model,
imh.prop(
"l1",
imh.prop(
"l2",
imh.prop(
"l3",
imh.prop("l4", () => 2)
)
)
)
);
imh(model, imh.prop("l1", imh.prop("l2", imh.prop("l3", imh.set("l4", 2)))));
imh(model, imh.prop(["l1", "l2", "l3", "l4"], imh.val(2)));
imh(
model,
imh.prop(["l1", "l2", "l3", "l4"], () => 2)
);
imh(model, imh.prop(["l1", "l2", "l3"], imh.set("l4", 2)));
set(key, value)
imh({ name: "Peter" }, imh.set("name", "Spider Man"));
// => { name: 'Spider Man' }
imh([1, 2, 3], imh.set(1, 4));
// => [1, 4, 3]
unset(...keys)
imh({ prop1: 1, prop2: 2, prop3: 3 }, imh.unset("prop1", "prop2"));
// => { prop3: 3 }
imh([1, 2, 3], imh.unset(1, 2));
// => [1, undefined, undefined]
merge(...values)
imh({ p1: 1, p2: 2 }, imh.merge({ p1: 1, p2: 2 }));
// => { p1: 1, p2: 2 } nothing to change
imh({ p1: 1, p2: 2 }, imh.merge({ p1: 1 }, { p2: 2 }));
// => { p1: 1, p2: 2 } nothing to change
imh({ p1: 1, p2: 2 }, imh.merge({ p1: 5 }, { p1: 1 }));
// => { p1: 1, p2: 2 } nothing to change
imh({ p1: 1, p2: 2 }, imh.merge({ p3: 3 }));
// => { p1: 1, p2: 2, p3: 3 }
String
replace(findWhat, replaceWith)
imh("banana, apple, watermelon, banana", imh.replace("banana", "orange"));
// => 'orange, apple, watermelon, banana`
imh("banana, apple, watermelon, banana", imh.replace("banana", /orange/g));
// => 'orange, apple, watermelon, orange`
upper()
imh("Oop!!!", imh.upper());
// => OOP!!!
lower()
imh("Oop!!!", imh.lower());
// => oop!!!
Misc
add()
imh(1, imh.add(9));
// => 10
imh(
new Date(2000, 1, 1),
img.add({
years: 1,
months: 1,
days: 1,
hours: 12,
minutes: 12,
seconds: 12,
milliseconds: 900,
})
);
// => 2001/02/02 12:12:12:900
imh(
// unix timestamp
new Date(2000, 1, 1).getTime(),
img.add({
years: 1,
months: 1,
days: 1,
hours: 12,
minutes: 12,
seconds: 12,
milliseconds: 900,
})
);
// => unix timestamp
toggle()
imh({ completed: false }, imh.prop("completed", imh.toggle()));
// => { completed: true }
result(callback)
Get result of last mutation. It is often used with splice() / pop() / shift()
let state = {
sourceList: ["item 1", "item 2"],
destList: ["item 3", "item 4"],
};
function move(index, count) {
state = imh(state, [
// remove some from sourceList
imh.prop("sourceList", imh.splice(index, count)),
// append to destList
imh.result((result) => imh.prop("destList", imh.push(...result))),
]);
}