FluxライブラリAltのデータフローについて


元記事:Alt Flux Tutorial in Depth – The First Cycle

FluxライブラリにAltを使っています。もともとはRefluxをつかっていたんですけど、DefinitelyTypedに定義ファイルが無いことに気づき、すでにDefinitelyTypedに定義ファイルがあるFluxライブラリはないかな〜って探してたところ、Altを見つけた次第です。(深い理由ではない。。。)

Altのチュートリアルをみてみたんですけど、個人的に微妙にトリッキーな部分があったので、理解を深めるため、忘れないために備忘録を残しておきます。 理解が誤っている点など、ご指摘いただけるととってもうれしいです

概要

まず、頭の中を整理する為に図示しようとしたんですけど、Alt側で 自動的に ごにょごにょしている場所とかあるので、若干初見ではソースが追いづらい印象を受けました。というのはさておき、ComponentActionStoreの関係は概ね下の図みたいになるかと思います。

詳細

View

まず、Locationsコンポーネントがマウントされて、componentDidMountにてLocationの一覧を取得する為にLocationStore.fetchLocationsが実行されます。LocationStoreのコードを見てる人なら、いつfetchLocationsなんて定義したんだ?って最初は思うでしょうけど、そこについては次節にて説明します。いったんViewで行われていることはこれだけと思ってOK。

// Locations.jsx
var Locations = React.createClass({
  componentDidMount() {
    LocationStore.fetchLocations();
  },

  render() {
    // edited for brevity
  }
});

Action, Dispatcher, Store

LocationStorefetchLocations関数については、AltexportAsyncについてわかっていないとコードを追うのが困難になります。exportAsyncで指定したSource(今回の場合LocationSource)で定義されている関数は、そのStoreの関数かのように使えるようになります(mixinのようなイメージ)。この挙動についての詳細はdocsの一読をおすすめします。ちなみにこの設定はLocationStoreconstructorで行われています。↓

// LocationStore.js
class LocationStore {
  constructor() {
    // 省略

    // ここでLocationSourceの関数がLocationStoreの関数として
    // 公開される
    this.exportAsync(LocationSource);
  }
  // 省略
}

以上のことから、ViewでLocationStore.fetchLocations()が呼ばれた時は、実際にはLocationSourcefetchLocationsが実行されます。Altの世界でSourceはサーバとのデータやりとりを行う場所(ajax的ないろいろが発生する場所)になります。Sourceには色々と定義があるのですが、初回サイクルで注目すべきはremoteloadingの2つです。remoteはサーバにリクエストを投げる部分です。なのでPromiseが返却される場所になります。loadingは、fetchLocationsが呼ばれたときに発火するActionを定義します。今回であればLocationActions.fetchLocationsを発火させることで、fetchLocations Actionに興味がある人に通知することができるのです。

// LocationSource.js
var LocationSource = {
  fetchLocations() {
    return {
      remote() {
         // サーバ通信部分。データ取得依頼してPromiseを返却する場所
        return new Promise(function (resolve, reject) {
          // 省略
        });
      },

      // 省略

      // fetchLocationsの時に発火するActionの定義 
      loading: LocationActions.fetchLocations 
    }
  }
}

ここまでを図示するとこんな感じに書けるかと思います。

次にLocationActions.fetchLocationsアクションが発火することで、LocationStoreのハンドラ(handleFetchLocations)が実行されます。この中では、Locationを再取得するので既存のthis.locationsを空の配列にしています。

class LocationStore {
  constructor() {
    // 省略

    // listening and registering event handlers
    this.bindListeners({
      handleUpdateLocations: LocationActions.UPDATE_LOCATIONS,
      handleFetchLocations: LocationActions.FETCH_LOCATIONS, 
      handleLocationsFailed: LocationActions.LOCATIONS_FAILED,
      setFavorites: LocationActions.FAVORITE_LOCATION
    });

    // 省略
    //
    this.exportAsync(LocationSource);
  }

  // 省略

  handleFetchLocations() {
    // fetchLocations時にはthis.locationsを空の配列に戻す
    this.locations = [];
  }

  // 省略
}

View再び

LocationStorestate(this.locations)が変わることで、AltContainerを通してAllLocationsに新しいprops(locations)が渡されます。AltContainerについてはAltを使う上ではきっと知っておかないといけない仕組みです。高階層Componentで、一番賢いComponentというイメージでしょうか(propsのエントリポイント的なポジション?)。StoreComponentをいい感じに繋げてくれて、Storestateに変化があった場合に自動的にComponentにpropsとして渡してくれるような仕組みです。が、かなり色々できるみたいなので、ただStoreComponentをつなげてくれる人っていうわけではないです。(詳細は私も勉強中。。。)

話を戻すと、AllLocationsに新しいprops(locations)が渡されるのでAllLocationsコンポーネントのrenderが走ります。この時、LocationSourcePromiseがまだresolveなりしてないので、LocationStore.isLoading()trueを返します。ゆえに、JSXはajax-loader.gifを生成して、読み込み中の画面が生成されます。ちなみにこのLocationStore.isLoading()についてもexportAsync(LocationSource)したときに自動的に公開される関数なので覚えておく必要ありです。なにかしらSourceがペンディング中のリクエストがあるかどうか判定してくれる便利関数です。

var AllLocations = React.createClass({
  // 省略

  render() {
    // 省略

    // LocationSourceのPromiseが解決していないので、ここはtrueになる
    if (LocationStore.isLoading()) {
      return (
        <div>
          <img src="ajax-loader.gif" />
        </div>
      )
    }

    // 省略
  }
});

var Locations = React.createClass({
  componentDidMount() {
    LocationStore.fetchLocations();
  },

  render() {
    return (
      <div>
        <h1>Locations</h1>
        <AltContainer store={LocationStore}>
          // AltContainerのおかげでLocationStoreとAllLocationsコンポーネントが
          // いい感じにつながってくれる
          <AllLocations />
        </AltContainer>

        <h1>Favorites</h1>
        <AltContainer store={FavoritesStore}>
          <Favorites />
        </AltContainer>
      </div>
    );
  }
});

StoreとComponentについて図示すると以下のようになります。

そして、画面は読み込み中状態が表示されます。

ここまでがLocation取得時の初回サイクルで起きる出来事です。次回はPromiseが解決したときに何が起きているかについてまとめます。

ここまで読んで下さりありがとうございます。
見落としている点、説明が誤っている点があればご指摘お願いしますm(__)m