不変なオブジェクトを扱う immutable.js を使ってみます。最近流行りの React とも相性がいいはずです。

インストール

npm でインストールできます。例を ES6 で書くために babel もインストールしておきます。

$ npm install -g babel
$ npm install immutable

例のコードは babel-node で実行していきます。

List

immutable.js で Array にあたるオブジェクトが List になります。

Array と同じように pushpop で要素の追加と削除ができます。少し違うのが返り値が新しい List だということです。

import { List } from 'immutable';

const list1 = List([1, 2, 3]);
const list2 = list1.push(4);
const list3 = list1.pop();

console.log(list1); // List [ 1, 2, 3 ]
console.log(list2); // List [ 1, 2, 3, 4 ]
console.log(list3); // List [ 1, 2 ]

このように list1pushpop を呼び出しても list1 自身は変更されず、新しいオブジェクトが作られます。 shiftunshift も同様です。

オブジェクトが不変だと他の場所でオブジェクトが変更されている可能性がないので、プログラムで考慮する必要のある状態が減り、結果的にバグなども減らせます。

map filter reduce などの動きは、ほとんどそのままなので大丈夫だと思います。 push などは Array とは動きが違ってしまうので、なれないと使いづらいかもしれません。

Map

{} や ES6 の Map に対応するのは Map です。

ES6 の Map と同じように get set で値の取得と設定ができます。

import { Map } from 'immutable';

const map1 = Map({ a: 1 });
const map2 = Map([['b', 2]]);
const map3 = map1.set('c', 3);

console.log(map1); // Map { "a": 1 }
console.log(map2); // Map { "b": 2 }
console.log(map3); // Map { "a": 1, "c": 3 }

List と同じように set が返すのは新しい Map です。

map filter reduce など他のメソッドも使えます。

map1a だけ変更したい場合は map1.update('a', (x) => x * 2) などとすれば a が変更された新しい Map が作られます。

React と組み合わせて

React と組み合わせて ToDo リストみたいなものを作ってみます。

import { List, Map } from 'immutable';
import * as React from 'react';

var App = React.createClass({
    getInitialState() {
        return {
            tasks: List([
                Map({ id: '1', done: false, name: '牛乳を買う' }),
                Map({ id: '2', done: false, name: 'はがきを出す' })
            ])
        };
    },
    render() {
        return (
            <ul>
                { this.state.tasks.map(task =>
                    <li key={ task.get('id') }>
                        <label>
                            <input type="checkbox" checked={ task.get('done') }
                                onChange={ this.toggleDone.bind(this, task.get('id')) } />
                            <span>{ task.get('name') }</span>
                        </label>
                    </li>
                ) }
            </ul>
        );
    },
    toggleDone(id) {
        this.setState({
            tasks: this.state.tasks.update(this.state.tasks.findIndex(t => t.get('id') === id),
                t => t.update('done', d => !d))
        });
    }
});

React.render(<App />, document.body);

タスク一覧を表示するだけのプログラムです。

setState の部分では update などのメソッドを駆使してタスクのフラグを切り替えています。操作に対応するメソッドが提供されているので、それを使って state を組み立てることができます。

まとめ

  • immutable.js は不変なオブジェクトを提供する
  • 不変なオブジェクトはバグを防ぐかもしれない
  • React とも組み合わせて使える