なかなかクールなライブラリだったので紹介します。
今までのライブラリと何が違うのか
今までのイミュータブルライブラリといえばImmutable.jsやMoriがあります。これらは「イミュータブルな配列」や「イミュータブルなMap」を提供するライブラリです。独自の配列やMapなので、通常の配列やオブジェクトのような書き方ができません。
const { List } = require('immutable'); const list = List([1, 2, 3]); const arr = [1, 2, 3]; list.get(0); // c.f. arr[0];
arr[0]
やobj.key
のような書き方でアクセスできません。独自の書き方を強制されるのはかなりのストレスです。
それに対し、Immerは「JavaScriptのオブジェクトへのイミュータブルな操作」を提供します。
const produce = require('immer').default; const arr = [1, 2, 3]; const pushedArr = produce(arr, draft => { draft.push(4, 5, 6); draft[0] = 0; }); console.log(arr); // [1, 2, 3] console.log(pushedArr); // [0, 2, 3, 4, 5, 6]
ご覧のように、元のオブジェクトは変更されず、返り値は破壊的変更を加えたものとなります。
返り値はただの配列なので、当然普通の配列として扱うことができます*1。
何が嬉しいか
まず、扱う対象が普通のオブジェクトであり、返されるものも普通のオブジェクトのため通常のJavaScriptの書き方ができます。
また、produce()
の中の記述は破壊的変更が可能なので、可読性が高いです。
さらに、独自の配列を追加したり独自のAPIを強制しないので、今までのコードに組み込むことも容易です。
試しにイミュータブル版メソッドを追加する
const produce = require('immer').default; const immer = { push: Symbol('push'), pop: Symbol('pop') }; Array.prototype[immer.push] = function(...args) { return produce(this, draft => { draft.push(...args); }); }; Array.prototype[immer.pop] = function() { return produce(this, draft => { draft.pop(); }); }; const arr = [1, 2, 3]; console.log(`push: ${arr[immer.push](4, 5, 6)}`); console.log(`pop: ${arr[immer.pop]()}`); console.log(`original: ${arr}`);
欠点
まだ開発途上のため、配列とプレーンオブジェクトしか扱えません。例えばDate()
やnew MyClass()
ではうまく動作しません。Map
やSet
も無理です。今後に期待ですね
*1:注: ただし、freeze済みのオブジェクトが返されますので、破壊的変更を加えることはできません。設定でfreezeしないようにもできます