コネヒト開発者ブログ

コネヒト開発者ブログ

immutableのメリットとImmutable.jsでのModel定義

f:id:dachi023:20160902200209p:plain

こんにちは。フロントエンジニアの安達 (@ry0_adachi) です。
気付いたら前回の私の記事から2ヶ月が経ちました。時間の流れは早いですね...。

さて、今回はimmutableとそれをJSで実現するためのImmutable.jsについてです。
この記事を通して沢山の方にimmutableについて知ってもらえると嬉しいです。

immutableとは

immutableは元となっているオブジェクトに変更を加えない、加えられない状態です。
また、変更を加える、加えられることをmutableと呼びます。

immutableの例

JSで配列に値を追加する際に

let list = [1, 2]
list.push(3)

とやるとlistに3が追加されます。
これは元となっているlistに対して3を追加するという変更を加えているのでmutableです。

しかし、これをImmutable.jsのListで実装すると

let list = Immutable.List.of(1, 2)
let newList = list.push(3)

となり、元となっているlistに3を追加した新しいListが生成されます。
この場合はlistに対して変更を加えないのでimmutableである、と言えます。

immutableであることのメリット

それでは、immutableであることのメリットは何でしょうか?

私は影響範囲の拡大を防ぐことが最大のメリットだと思っています。

参照しようとしているオブジェクトにどこかの処理で意図しない変更がされた場合に、その後に続く処理はその変更の影響を受けた状態でオブジェクトが渡ってきてしまいます。 これは定義(または初期化)した時の状態から変更を加えられることが原因です。

let list = [1, 2]
list.shift() // listに変更を加える処理が実行される
console.log(list) // 期待していた定義時の構造ではなくなっている

オブジェクトがimmutableであればこのような事態を手軽にある程度防ぐことが可能です。大きく修正が必要なケースはほぼ無くなるんじゃないかな、と思っています。

ちなみに「ある程度」といったのはlet, varで定義した変数への再代入は可能なためです。より厳密に不変であるとするならconstを使って防ぐなどしても良いかもしれません。

const list = Immutable.List.of(1, 2)
list = null // constで定義されているので再代入は不可

Immutable.js

github.com Immutable.jsはfacebook製のimmutable(不変)なオブジェクトを提供するJSライブラリで、List, Map, Seq, Set, StackといったimmutableなCollectionが簡単に扱えます。この辺の使い方は難しくないのでドキュメントを読むだけでも十分だと思います。

Collectionだけでも十分に魅力的なImmutable.jsなのですが、今回はImmutable.jsを更に活用するためのRecordという機能を紹介します。

Recordの機能

Recordはメソッドやプロパティを自分で定義してClassを生成することが可能で、ロジックを持つ必要があるModelなどを実装したい時に非常に便利です。

Classの定義

まずはTodoクラスを生成する時のコードを書いてみます。

const TodoRecord = Immutable.Record({
  id: null,
  completed: null,
  text: null
})

class Todo extends TodoRecord {
  constructor(text) {
    super({
      id: Date.now(),
      completed: false,
      text
    })
  }
}

const todo = new Todo('text')

Recordの定義をして、更にそのRecordを継承してClassを宣言します。
Classなのでメソッドやプロパティを持たせたりすることが可能です。

データの編集

textを編集してみます。
Recordにset関数が実装されているので簡単な変更であればこれで事足ります。

const todoB = todo.set('text', 'second text')

もちろん元になっているtodoとは別のオブジェクトを生成して返してくれます。
なので、todo.texttextのままでtodoB.textsecond textになっています。

ロジックが必要な場合はRecordにメソッドを追加してあげると綺麗です。

Recordの値をAPIに投げたい時

ModelをAPIに投げたい時はtoJS()でObjectに戻してあげると良いです。

todo.toJS() // => { id: 1234567890, completed: false, text: 'text' }

RecordをCollectionに格納する

ListやMapなどと組み合わせることももちろん可能です。

const list = Immutable.List.of(todo)
const ListB = list.push(todoB)

通常のArrayやObjectなどに格納することもできますが、一緒に使うとimmutableなのかmutableなのかで混乱してきそうなのでImmutable.jsのCollectionと組み合わせた方がいいです。

さいごに

CollectionもRecordも非常に便利で、それに加えてimmutableで提供されるというのが良い点だと思いました。お陰で今後コードが肥大化してもメンテしやすい気がします。あとはデータ操作のロジックをModelに逃がすことでロジックの集約が楽になったのと各データの持つプロパティが一目で分かるのも結構嬉しいです。

読んでいただいた皆さんの参考になっていれば嬉しいです。Immutable.jsに限らず、immutableなライブラリを是非使ってみてください。

参考資料