【facebook/flux】MapStoreを使ってImmutableなFluxを実装する

先日『Flux UtilsでのFlux実装方法を超シンプルなサンプルを使って解説』という記事を投稿しました。こちらの記事では、Facebook製のFluxフレームワークであるFlux Utils(facebook/flux)で用意されているユティリティクラスのうちReduceStoreクラスを使ってサンプルを作りました。今回は、Fluxの「state」をイミュータブルなMapとして定義できるMapStoreクラスを使うと、前回のサンプルをどう書き換えることができるか紹介したいと思います。

はじめに

まず、今回の記事で扱うサンプルは以下となります。フォームに入力したテキストを表示させるだけのとても単純なものとなっています。

それから、今回の記事は先日投稿した『Flux UtilsでのFlux実装方法を超シンプルなサンプルを使って解説』を元に書いていくので、事前にこちらの記事も目を通しておいてください。

またFlux Utils(facebook/flux)についての詳細は以下をご参照ください。

Flux Utilsの「MapStore」クラス

Flux Utilsは、Fluxのフレームワークであり、Fluxを構成する「View」「Action」「Dispatcher」「Store」の4つの部分のうち「Store」と「View」の部分を強化するユーティリティとなります。

その中で「Store」向けユーティリティクラスとして、以下の3つのクラスが用意されています。

  • Storeクラス: dispatcherを受け取り、storeのインスタンスの作成と登録を行うベースクラス
  • ReduceStoreクラス: stateを作成/保持し、actionをreduceしてstateを更新するクラス(Storeクラスを継承している)
  • MapStoreクラス: stateをimmutableなMapとして定義するクラス(ReduceStoreを継承している)

MapStoreクラスは上記の通り、Storeクラス、ReduceStoreクラスを継承し、さらにFluxの「state」をイミュータブルなMapとして定義するための機能を持たせたクラスとなっています。

MapStoreクラスのAPIは以下となります。

イミュータブルとは

イミュータブルとは、「変更不可な」ことを言います。Wikipediaでは以下のように説明されています。

オブジェクト指向プログラミングにおいて、イミュータブル(immutable)なオブジェクトとは、作成後にその状態を変えることのできないオブジェクトのことである。 対義語はミュータブル(mutable)なオブジェクトで、作成後も状態を変えることができる。

MapStoreは、FluxのstateをイミュータブルなMapとして定義するためのクラスとなりますが、このイミュータブルにすることが果たして何に役立つのでしょうか。

FluxやReactでstateをイミュータブルにする意味

FluxやReactでは、stateで状態の管理をし、stateが変更されるとviewを更新するような仕組みとなっています。つまり、stateの値を常に比較する必要があります。stateがイミュータブルになっていると、stateを変更する場合は、そもそも変更ができないので、必ずコピーを作り、そのコピーを変更することとなります。そうすることによって、stateの前後の状態が保証されるので、比較がしやすくなり、その結果も確実なものとなります。

[参考]

例えば、以下のようにイミュータブルなオブジェクトを使わない場合は、一見stateとstate2は別物となると思いますが、同じものとみなされてしまいます。

var state = {
  'foo': 'bar'
}

var state2 = state;
state2.foo = 'baz';

console.log(state === state2); // => ture
console.log(state.foo); // => baz

JavaScriptのオブジェクトは、代入は「参照渡し」となるため、代入先のオブジェクトを変更すると、代入元のオブジェクトも変更されるようになっているからです。

オブジェクトをイミュータブルにしてみると、以下のように確実にオブジェクトの変更を捉えることができるようになります。

var state = Map([
  ['foo', 'bar']
]);

var state2 = state.set('foo', 'baz')

console.log(state === state2); // => false
console.log(state.get('foo')); // => bar

つまり、stateがイミュータブルでない場合(ミュータブルな場合)、DispatchされるたびにStore内のreduce()メソッドが実行され、常にデータの更新が行われるようになってしまい、パフォーマンス的にもあまりよろしくありません。

Immutable.jsを使う

MapStoreクラス内でstateをイミュータブルにするには、Immutable.jsを使います。Immutable.jsはFacebook製のイミュータブルなデータを扱うためのJavaScriptライブラリです。Flux Utilsは、dependenciesとしてImmutable.jsに依存しているので、Flux Utilsをインストールすると、Immutable.jsも一緒にインストールされます。

npmのバージョンによっては、flux直下のnode_modulesにインストールされている場合もあります。個別にインストールする場合は以下のコマンドを実行します。

$ npm install --save immutable

MapStoreクラスを使う場合は、このImmutable.jsも読み込む必要があります。

import Immutable from 'immutable';

Immutable.jsの詳細は以下などをご参照ください。

変更箇所は以下の3箇所

MapStoreクラスを実装する際のポイントは以下3点となります。先日投稿した『Flux UtilsでのFlux実装方法を超シンプルなサンプルを使って解説』で紹介したソースコードを元に説明するので、こちらも一度目をお通しください。

モジュールの読み込み部分の変更

モジュールの読み込み部分では、Immutable.jsとflux/utilsからMapStoreクラスを読み込むようにします。

import React, { Component } from 'react';
import { render } from 'react-dom';
import Immutable from 'immutable';
import { Dispatcher } from 'flux';
import { MapStore, Container } from 'flux/utils';

Storeの変更

Storeの変更は、以下の3点となります。

  1. 作成するStoreのclassの継承元をMapStoreにする
  2. Imuutable.jsを使って初期stateを作成する
  3. データの更新時にはImuutable.jsのset()メソッドを使う
// Store
class FormStore extends MapStore { // ...1

  getInitialState() {
    return Immutable.Map([ // ...2
      ['value', null]
    ]);
  }

  reduce(state, action) {
    switch (action.type) {
      case act.SEND:
        return state.set('value', action.value); // ...3
    }
  }

};

// Storeのインスタンス生成
const formStore = new FormStore(dispatcher);

ポイントとしては、MapStoreクラスを使うことで、イミュータブルなstateに変更があれば、複製が作成され、stateが置き換わるようになります。

View (Container)の変更

Immutable.jsのget()メソッドを使ってStoreのstateの値を取得し、view側のstateにセットするようにします。

// View (React Component)
class FormApp extends Component {
  static getStores() {
    return [formStore];
  }
  static calculateState(prevState) {
    return {
      'value': formStore.get('value')
    };
  }

  render() {
    console.log(this.state);
    return (
      <form>
        <FormInput />
        <FormDisplay data={this.state.value} />
      </form>
    );
  }

};

// Container
const FormAppContainer = Container.create(FormApp);

まとめ

簡単ではありますが、MapStoreクラスを実装する方法を紹介してきました。ソースコードの全体はGitHubにアップしているので、こちらも合わせてご確認いただければと思います。

上でも述べた通り、stateをイミュータブルなものにすることで、stateの前後の状態が保証されるようになります。そうすることでstateの前後の比較がしやすくなり、その結果も確実なものとなります。結果として、システム全体のパフォーマンスの向上にもつながっくるのではないでしょうか。

Flux Utilsについて色々見ているところですが、なかなか面白いですね。Facebookの型チェックツールのFlowと組み合わせることで、より堅牢なシステムの開発にも対応するようになったりします。この辺またそのうちまとめたいと思います。

コメント一覧

  • 必須

コメント