![[初心者向け]Fluxを一周する](https://devio2023-media.developers.io/wp-content/uploads/2014/04/js.png)
[初心者向け]Fluxを一周する
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
Fluxとは
Facebook社が提唱した、UI管理のためのアーキテクチャです。Webアプリケーションに限らず、様々なフロントエンド環境に適用できる考え方です。

画像出典 https://facebook.github.io/flux/docs/in-depth-overview.html#content
この記事ではFluxそのもの詳細には触れません。英語・日本語を問わず良質な記事がたくさんあるので、気になった方は検索してみてください。
今回は、ネット上にたくさんあるドキュメントやWebサイトを参考にして、Fluxを一通りやってみた内容になります。
重要なのは、常に以下の順序で動作し、例外は無いということです。
- Actionが発生する
- Storeの値が変更される
- Viewが書き換えられる
最初にレンダリングされたあと、マウス操作などでActionが発行されると、設定したとおりにStoreの値が変更され、Viewの変更までが行なわれます。 ViewからStoreの値を変更するのではなく、Actionを実行することで結果としてStoreが変更されるという状態を維持します。
なお、ReactJSは上の図のViewのみを担当します。
Fluxエコシステムについて
Flux自体は考え方に過ぎず、多くの実装が公開されています。その中でも以下の2つの実装が有名です。
Reduxはサードパティ製のライブラリでしたが、最近確認したらreactjs organizationに入っていました。デファクトスタンダードだったものが、認知されてスタンダードになったのかもしれません。
アプリケーションを作るときに必要な機能が全て入っているそうで、ドキュメントもちゃんと整備されているようです。
一方のfluxは、本家Facebookによるリファレンス実装です。ドキュメントにも、最低限の機能しかないという旨が書かれています。
flux自体はDispatcherの機能しかありませんが、最近公開されたflux/utilsにはStoreなどの機能が追加されています。
今回は学習のため、簡単そうなfluxを使ってみます。以後、概念をFlux、実装をfluxと表記します。
実装
準備
環境を整備して、以下の準備をしてください。
- create-react-appでreactjsアプリケーションを作成して、動作確認までを行なう
- yarn add flux immutableを実行する
今回はWebSocketサーバに接続するサンプルを試してみました。サーバ側のコードは割愛します。
Containerを導入する
Fluxの図にはありませんが、fluxにはContainerという機能があります。これは、Storeのstateをview用のpropsに変換するものです。Viewと同様、React.Componentを継承したクラスを作成します。UI管理やstate操作をしない、各Componentの親になるComponentのようなものです。
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import AppContainer from './AppContainer';
import './index.css';
// ただContainerをレンダリングするだけです。
// 実際のViewは、Containerの中でレンダリングしてます。
ReactDOM.render(
  <AppContainer />,
  document.getElementById('root')
);
AppContainer.js
import React, {Component} from 'react';
import {Container} from 'flux/utils';
import AppView from './AppView';
import ConnectionStore from './ConnectionStore'; // WebSocketでつながるアプリケーションを想定してます。
class AppContainer extends Component { // Viewと同様、React.Componentを継承します
  // stateの変更を監視するstoreを持つ、flux/utilsのReduceStoreを継承したクラス登録します
  static getStores() {
    return [
      ConnectionStore,
    ];
  }
  // stateのgetterを登録します
  static calculateState() {
    return {
      connection: ConnectionStore.getState(),
    };
  }
  // ContainerはあくまでViewです。ただ他の Viewの親であるというだけです。
  // なので、ここで他のComponentをレンダリングします。
  render() {
    // AppViewでstateの値をpropsとして受け取れるように渡します。
    return <AppView connection={this.state.connection}/>
  }
}
export default Container.create(AppContainer);
AppView.js
import React, { Component } from 'react';
import ServerActions from './ServerActions'; // アプリ独自のActionです。
import './App.css';
class AppView extends Component {
  handleClick(e) {
    e.preventDefault();
    ServerActions.connect();
  }
  render() {
    return (
      <div className="App">
        // onClickでActionを呼び出します。
        <button id="connect" onClick={this.handleClick.bind(this)}>connect</button><br/>
        // Containerで渡された値(もとはstate)は、このComponentではpropsとして参照できます。
        <div>MESSAGE: {this.props.connection.get('message')}</div>
      </div>
    );
  }
}
export default AppView;
DispatcherとStoreとActionを作成する
AppDispatcher.js
import {Dispatcher} from 'flux';
export default new Dispatcher();
ConnectionStore.js
import {ReduceStore} from 'flux/utils';
import Immutable from 'immutable';
import AppDispatcher from './AppDispatcher';
import ServerActionTypes from './ServerActionTypes';
class ConnectionStore extends ReduceStore {
  // DispatcherにStoreを登録します。
  constructor() {
    super(AppDispatcher);
  }
  // 初期のStateを作成します。ここで返したオブジェクトに、reduceメソッド内からstateという名前でアクセスできます。
  // ここではImmutable.jsを使って値を作成しています。
  // MapStoreというものがImmutableなMapを作成する役割を担っていたらしいのですが、しばらく前にAPIが削除されてました。
  // https://github.com/facebook/flux/pull/405
  getInitialState() {
    return Immutable.Map({
      ws: null,
      message: 'Initialized',
    });
  }
  // actionを参照してstateを更新する対応表を実装します
  reduce(state, action) {
    switch (action.type) {
      case ServerActionTypes.CONNECTED:
        state.set({ws: action.ws});
        // 新しいstateを返します。
        // stateメソッドで得られる既存のImmutable.Mapではなく、Immutable.jsのupdateメソッドにより新しいオブジェクトを作成して返しています。
        // 返した新しいオブジェクトは、以後stateメソッドで参照できます。
        // Containerは監視対象Storeが変更されたら当該Componentを再描画します。
        return state.update('message', () => 'Connected');
      default:
        return state;
    }
  }
}
export default new ConnectionStore();
ServerActions.js
import AppDispatcher from './AppDispatcher';
import ServerActionTypes from './ServerActionTypes';
const ServerActions = {
  connect() {
    // websocket周りは省略
    // JSXで指定されるアクションを実装します。
    // DispatcherにはStoreが登録されているので、識別子(type)を指定して引数(今回はws)を指定すれば良いです。
    AppDispatcher.dispatch({
      type: ServerActionTypes.CONNECTED,
      ws: ws,
    });
  }
};
export default ServerActions;
ServerActionSypes.js
const ServerActionTypes = {
  CONNECTED: 'CONNECTED',
};
export default ServerActionTypes;
まとめ
もっと複雑なインタラクションや、自動テストを考慮すると、今回の記事の内容では足りないかもしれません。いずれ実際のプロジェクトで使うときのため、引き続き勉強していこうと思います。










