Fluxアーキテクチャのサイクルをコードリーディングで一通り確認する


FluxはFacebookが提唱したUIアーキテクチャです。この文書では、React JS Tutorialsのソースコードをコードリーディングすることで、Fluxアーキテクチャのサイクルを一通り確認します。

Fluxの図:

準備

node.jsのインストールは完了しているものとします。

React JS Tutorialsのソースコードをダウンロードしてください(※バージョン違いが生じないようにフォークしてあります)。

ダウンロードしたらプロジェクトのルートディレクトリで、

cd 3-flux/
npm i
npm run dev

します。

そして、localhost:8080をブラウザで開いてください。

下記のようなページが表示されたら準備完了です。

コードリーディング

画面とFluxアーキテクチャの関係を確認する

src/js/pages/Todos.jsを開いてください。今ブラウザに表示されているTodoリスト画面が定義されています。この画面はFluxアーキテクチャと4箇所で関係しますので、順に見ていきます。

一つめの箇所です。12行目を見てください。

this.state = {
  todos: TodoStore.getAll(),
};

このTodoStoreはFluxのStoreです。ここからデータを取得し、stateの初期値にセットしています。

次に二つめの箇所です。25行目のgetTodos()を見てください。

  getTodos() {
    this.setState({
      todos: TodoStore.getAll(),
    });
  }

ここでもTodoStore(FluxのStore)からデータを取得し、stateを更新しています。

このgetTodos()が実行されるのが三つめの箇所です。

17行目を見てください。

  componentWillMount() {
    TodoStore.on("change", this.getTodos);
  }

  componentWillUnmount() {
    TodoStore.removeListener("change", this.getTodos);
  }

componentWillMountでTodoStore(FluxのStore)が変化した時にgetTodos()をするように登録をしています。componentWillUnmountの時には登録を解除しています。

ここでFluxの図との関係を確認しましょう。図の「React Views」は今見ているTodosコンポーネントです。図の「Store」がTodoStoreです。図の「Change Event & Store Query」は「Change Event」「Store Query」の二つに分けることができ、「Change Event」 は下記です(17行目、再掲)。

  componentWillMount() {
    TodoStore.on("change", this.getTodos);
  }

  componentWillUnmount() {
    TodoStore.removeListener("change", this.getTodos);
  }

「Store Query」は下記です(25行目、再掲)。

  getTodos() {
    this.setState({
      todos: TodoStore.getAll(),
    });
  }

つまり「Store」の「Change Event」を「View」が購読登録し、「Change Event」が発生したら「Store Query」を実行して、更新されたデータを読み取りにいくわけです。

ただし、初回だけは特別に「Change Event」の発生にかかわらず、データを読み込んでいます(12行目、再掲)。

this.state = {
  todos: TodoStore.getAll(),
};

コードリーディングに戻りましょう。31行目を見てください。これがTodosコンポーネントがFluxと関係している部分の四つめの箇所です(これで全部です)。

  reloadTodos() {
    TodoActions.reloadTodos();
  }

このTodoActions.reloadTodos()はUIでReloadボタンがクリックされたときに呼ばれています(44行目)。

<button onClick={this.reloadTodos.bind(this)}>Reload!</button>

Fluxの図で言う「User Interaction」はReloadボタンを押すことです。図の「Action Creaters」はこれにより呼ばれるTodoActions.reloadTodos()です。

ここまででTodosコンポーネントのFluxアーキテクチャとの関係を確認できました。

Fluxサイクルの確認

ここからはReloadボタンを押した時の動作に話を絞り、Fluxサイクルを確認していきます。

おさらいすると、Todosコンポーネント(図の「React Views」)でReloadボタンが押されると(図の「User Interactions」)TodoActions.reloadTodos()(図の「Action Creater」)が実行されるのでした。

Action Createrの実装を見て見ましょう。src/js/actions/TodoActions.jsの17行目を見てください。

export function reloadTodos() {
  // axios("http://someurl.com/somedataendpoint").then((data) => {
  //   console.log("got the data!", data);
  // })
  dispatcher.dispatch({type: "FETCH_TODOS"});
  setTimeout(() => {
    dispatcher.dispatch({type: "RECEIVE_TODOS", todos: [
      {
        id: 8484848484,
        text: "Go Shopping Again",
        complete: false
      },
      {
        id: 6262627272,
        text: "Hug Wife",
        complete: true
      },
    ]});
  }, 1000);
}

21行目でまずDispatcherにFETCH_TODOSアクションをdispatchします。22行目で(APIアクセスを擬似的に表現するため)setTimeoutが実行され、timeoutしたらRECEIVE_TODOSアクションをdispatchしています。

このDispatcherは一体どう言う動きをするのでしょうか。

src/js/dispatcher.jsを見るとFluxのライブラリを読み込んでいます。ここではFluxライブラリの実装に立ち入ることはせず、Dispatcherがどういう動きをするのかを説明するにとどめます。

import { Dispatcher } from "flux";

export default new Dispatcher;

Dispatcherにはコールバックを登録することができます。何らかのアクションがdispatchされると必ずコールバックが実行されます。

コールバックを登録している部分を確認します。src/js/stores/TodoStore.jsを見てください。55行目でコールバックを登録しています。

dispatcher.register(todoStore.handleActions.bind(todoStore));

そして38行目を見てください。コールバックの中ではActionの種類に応じてStoreをアップデートする動作が実行されます。

  handleActions(action) {
    switch(action.type) {
      case "CREATE_TODO": {
        this.createTodo(action.text);
        break;
      }
      case "RECEIVE_TODOS": {
        this.todos = action.todos;
        this.emit("change");
        break;
      }
    }
  }

このようにアクションのdispatchに応じてStoreが更新されます。

そして最初に説明した通り、Storeの更新に応じて「Change Events」が発行されて 「Store Query」 が行われ、「React Views」が更新されるということになります。

これでFluxアーキテクチャのサイクルを一通り確認することができました。

最後にFluxサイクルとコードの紐付けをまとめます(「React Views」から時計回りの順)

  • 「React Views」 = Todosコンポーネント
  • 「User Interaction」 = Reloadボタンを押すこと
  • 「Action Creaters」= TodoActions.reloadTodos()
  • 「Actions」=RECEIVE_TODOS
  • 「Dispatcher」 = FluxライブラリのDispatcher
  • 「callbacks」= TodoStoreにあるhandleActions()
  • 「Store」 = TodoStore
  • 「Change Event & Store Query」
    • 「Change Event」 = Viewが購読登録する、Storeの変化イベント
    • 「Store Query」 = ViewによるStoreの状態取得

以上