ES6のconstを使い倒すレシピ3 - immutable.js編 〜 JSおくのほそ道 #036

こんにちは、ほそ道です。

Object.freeze編でやったようにオブジェクトの状態を完全に凍結させたい場合はfreezeしてしまえばよいのですが、アプリケーションを作っていると当然、拡張/変更が必要になるシチュエーションは出てくると思います。
今回はライブラリを使って不意なデータ変更によって生まれる複雑性に対抗する方法を紹介していきます。

- [1.前提共有編](http://qiita.com/hosomichi/items/093aadcd8891c86e9dda)
- [2.Object.freeze編](http://qiita.com/hosomichi/items/84b05c1b0c09d26cd11e)
- [4.コレクション一発代入/一発返却編](http://qiita.com/hosomichi/items/843d1d4ec72ccc6691de)
- [5.条件分岐一発代入/一発返却編](http://qiita.com/hosomichi/items/119dc53980c8f61a0485)

[目次はこちら](http://qiita.com/hosomichi/items/ad9a9c99a4fda041a563)

## const宣言したオブジェクトの不変性を保ちながら変更に対応する

前回、deepFreezeという関数を作るときに、freezeされた新しいオブジェクトを生成して返すようにしましたが、そのようにして破壊的メソッドを使用せずに、変更を加えた新しいオブジェクトを返す、というようにするのが拡張/変更による想定外を生まないための一つの方法であると思います。

で、いろいろなオブジェクトに対して、より安全性を高めつつ拡張・変更を行うための一つの方策として、ほそ道は今のところfacebookのimmutable.jsを使うようにしてます。
これによって`const a = なんとか`の「なんとか」をコピーして拡張・子要素の変更を行いながら別のオブジェクトとして扱うことで、aはずっと「なんとか」のままであることが保証されます。

例えて言うと下記のように動作するものです。

```js:たとえ(これは動作しませんが、こんな感じ)
const a = {x: 1}
const b = a.set(x, 2);
console.log(b);  // {x: 2}
```

## immutable.jsに向かう前に

それ系の動作を提供するライブラリは幾つかあります。
ほそ道は、下記のようなライブラリたちを軽く検討しました。
一旦、facebookがリキ入れて作っているであろうということと、コレクションオブジェクトがレシーバーとなっている点がJS配列などと同じで直感的であろうということでimmutable.jsを使っていますが、その他のライブラリもそのうちしっかり触っていきたいなと思っています。

- [immutable.js](https://facebook.github.io/immutable-js/)
- [Ramda](http://ramdajs.com/)
- [mori](http://swannodette.github.io/mori/)
- [monet](https://cwmyers.github.io/monet.js/)

# immutable.js

今回はimmutable.jsの仕組みやメソッドについて深くは言及せずに、`const`とのコンビネーションという観点で紹介します。
今後、immutable.jsについては別枠でやろうかなと思っています。
immutable.jsには、`flatMapなど`通常のArrayが持っていないような処理系も持っているので、不変化以外にも魅力があります。

余談ですが、ほそ道はimmutable.jsをインポートする際に`I`という名前でインポートしてます。
公式だと`Immutable`でインポートしていますが、長いので`I`にしていますが、別段困るシチュエーションには遭遇していません。

```js:require
const I = require('immutable');
```

## immutable.List
例えば`Arrayのpush`は破壊的メソッドで、レシーバーの配列自体を変更してしまいますが、`immutable.jsのListのpush`は子要素を追加した新しいListを返し、レシーバーのListは変更されません。

```js:immutable.List
const a = I.List([1, 2, 3, 4, 5]);
const b = a.push(6);

console.log(a.toJS());    // [ 1, 2, 3, 4, 5 ]
console.log(b.toJS());    // [ 1, 2, 3, 4, 5, 6 ]
```

```js:Array
const a = [1, 2, 3, 4, 5];
a.push(6);
console.log(a);    // [ 1, 2, 3, 4, 5, 6 ]
```

### immutable.Record
Objectに相当するものとしては`Record`があります。
使い方がちょっと癖あるのでざっくりしたチュートリアルとともに。

`immutable.Record`はデフォルト値を表すオブジェクトを引数として、コンストラクタ関数を返す関数です。

```js:immutable.Record
const Rec = I.Record({x: 1, y: 2});
const a = new Rec();

console.log(a);        // Record { "x": 1, "y": 2 }
console.log(a.toJS()); //{ x: 1, y: 2 }
```

生成されたコンストラクタにオブジェクトを渡してインスタンスを生成すると、デフォルト値を上書きしたものが返されます。

```js:デフォルト値の上書き
const Rec = I.Record({x: 1, y: 2});
const a = new Rec({x: 10});
const b = new Rec({y: 20});

console.log(a.toJS()); // { x: 10, y: 2 }
console.log(b.toJS()); // { x: 1, y: 20 }
```

プロパティには`.`でアクセスできます。
`set`で値を変更して新しいオブジェクトを返します。**オリジナルは変更されません**。

```js:値の変更
const Rec = I.Record({x: 1});
const a = new Rec();

const b = a.set('x', 10);

console.log(a.x);      // 1
console.log(b.x);      // 10
```

`remove`を使うとプロパティはデフォルト値になります。`delete`とは違います。

```js:remove
const Rec = I.Record({x: 1});
const a = new Rec({x: 100});
const b = a.remove('x');
console.log(b.x);    // 1
```

で、大事なところなのですが新しい属性を拡張することはできません。
このオブジェクトが格納する可能性のある属性は初めのコンストラクタ生成時に全て宣言しましょう。という仕組みになっているのですね。

```js:拡張はできない。
const Rec = I.Record({x: 1});
const a = new Rec();
const b = a.set('y', 2);  // Error: Cannot set unknown key "y" on Record
```

### fromJS/toJS

`immutable.List`からArrayへの変換はtoJS一発です。
Arrayを受け取るようなメソッドへ渡す場合はこちらを使うと良いと思います。

```js:toJS
const list = I.fromJS([100, 200, 300, 400]);
const array = list.toJS();
console.log(array);    // [ 100, 200, 300, 400 ]
```

`fromJS`メソッドは`Array`から`immutable.List`への変換が可能です。

```js:fromJS
const list = I.fromJS([100, 200, 300, 400]);
console.log(list);    // List [ 100, 200, 300, 400 ]
```

ちなみにこのfromJSですが、通常のオブジェクトを食わせた場合は`Map`オブジェクトになります。

```js
const map = I.fromJS({x: 100, y: 200, z: 300});
console.log(map);                     // Map { "x": 100, "y": 200, "z": 300 }
console.log(map instanceof I.Map);    // true
console.log(map.get('x'));            // 100
```


---

と、いった感じで`const`によって宣言した値を想定外の変更が起こらないようにimmutable.jsを活用してみました。
アプリケーションの中にどう組み込んで行くべきかは今後にまとめ編として紹介してみたいと思います。