Clojureでブログやスマホアプリを作ってみる part2

こんにちは。リサーチ・アンド・イノベーションの小川(J-ogawa)と申します。

弊社サービスの「あなたの日常を、もっといい日常に変える」そんなアプリCODE
iPhoneアプリおよびサーバサイドの開発を担当してます。

本記事はシリーズ物で恐縮ですが、Clojureでブログやスマホアプリを作ってみようのコーナー、2回目となります。
前回 Clojureでブログやスマホアプリを作ってみる part1

基本方針

フレームワークre-frameを使用しています。
re-frameはとてもイケてるフレームワークだと思います。
Elmもre-frameに影響を受けています。

解説記事予定

全4回に分けて解説を行いたいと思います。

  1. re-frameの概要
  2. re-frameのdispatch時の副作用について
  3. サーバサイド
  4. re-frameでiOS & Androidアプリを作る

前回はre-frameについての概要を解説しました。
今回は、re-frameにおけるイベントについての考え方についてご紹介したいと思います。

dispatch(イベント)における副作用の有無

前回 dispatch -> update db(画面表示用ステート) -> subscribe -> 画面更新 と言う話をしました。
ここについてもう少し詳しく見ていきます。

ここでいうdbは画面表示用の単一のステートを指します

re-frameを使用すると否が応でも処理中の副作用について意識的になるように開発を進めることになります。
ここでいう副作用とは、dbの変更以外の事柄を指します。
まるで「dbを内界」、「それ以外を外界」としているような印象を私は受けました。

副作用なしの場合

dispatchによって、dbを変更します。それだけだと非常に簡単です

その場合、reg-event-dbを使います。

(re-frame/reg-event-db
 :switch-editor-mode
 (fn [db [_ mode]]
  (assoc-in db [:editor-mode] mode)))

第一引数:switch-editor-modeはこのアクションの名前です。
第二引数(fn [db [_ mode]] (assoc-in db [:editor-mode] mode)では「現状のdbアクションに渡された情報を引数にして変更後のdbを返す関数」を設定します。

この例は「editor-modeキーの値を引数modeの値にしたdb」を返すようにしています。単純ですね。

副作用ありの場合

例えば、loginのような処理を考えます。

この場合、処理の流れは以下となります。

  1. /loginにhttp POSTして返却値としてaccess_tokenを得る
  2. access_tokenをlocalStorageに保存
  3. /authaccess_tokenを投げて認証する
  4. dbをログイン状態に変更

これはdbを書き換える動作の前に3回の副作用が入っています。

loginで行う一連のコードを追っていく

副作用込みでdbに影響を与える操作はreg-event-fxを使います。

loginアクションのコードは以下です。
ちょっといきなりゴテっとしたコードが出てきますが、「httpを投げて、成功|失敗ならどうする」といった内容です。
(この後も流れに沿ってぽんぽんとコードが登場しますが、細部より流れを追っていただけるとと思います)

(re-frame/reg-event-fx
 :login
 (fn [{:keys [db]} _]
  (let [{email :email password :password} (:user-form db)]
   {:http-xhrio {:method          :post
                 :uri             "/login"
                 :params          {:email email :password password}
                 :format          (ajax/json-request-format)
                 :response-format (ajax/json-response-format {:keywords? true})
                 :on-success      [:login-success]
                 :on-failure      [:sign-error]}})))

({:keys [○]}destructuringと言う便利な変数バインド機能です。最初は取っつきにくいので読み飛ばしてください。値のdbキーの中身をdb変数にバインドしています)

db内のデータ(user-formのemailとpassword)をパラメータとしてPOSTして、成功したらlogin-successアクションをディスパッチします

次のステップlogin-successを見てみます
これも、副作用を伴ってdbを書き換える関数なので、reg-event-fxです。

(re-frame/reg-event-fx
  :login-success
    (fn [_ [_ {:keys [token]}]]
        {:store-token-localstrage token
         :dispatch [:auth]}))

これの意味は、store-token-localstrageアクションをやった後に、authアクションを行うと言う意味です。
2つのアクションを順に見ていきます。

store-token-localstrageはdbに影響を与えないシンプルな副作用(?)なのでreg-fxを使います。

(re-frame/reg-fx
  :store-token-localstrage
  (fn [token]
    (.setItem (.-localStorage js/window) :token token)))

JavaScriptっぽい部分が出てきました。単純に渡されているtokenをlocalstrageの:tokenキーに保存しています。

次のステップauthを見ます。これもまだ副作用を伴う処理です。GET /authしてます。

(re-frame/reg-event-fx
  :auth
  [(re-frame/inject-cofx :token)]
  (fn [{:keys [token]} _]
    {:http-xhrio (-> {:method          :get
                      :uri             "/auth"
                      :on-success      [:auth-success]
                      :on-failure      [:auth-error]}
                     wrap-default-http
                     (wrap-token-http token))}))

成功したら:auth-successアクションをします。ようやく副作用なしのreg-event-dbが来ました。

(re-frame/reg-event-db
  :auth-success
  (fn [db [_ res]]
    (-> db
        (assoc-in [:auth] true)
        (assoc-in [:show-login-modal] false))))

dbのデータをログイン後として、画面が書き換わります。
以上の流れを改めて書くと以下になります。

  • login (副作用あり)
    • login-success (副作用あり)
      • store-token-localstrage (副作用あり)
        • auth (副作用あり)
          • auth-success (副作用なし)
          • auth-error
    • sign-error

re-frameではこのように一連の流れを一つ一つに分けること、副作用とdb(画面更新)の処理を明確に区別することを意識させられます。

db外のものを扱う - 副作用の注入(?)

また、authアクションではaccess_tokenを処理に使用していますが、これはlocalStorageにあるものなのでdb外となります。
そういったものを扱う場合は副作用を注入するという考え方をします。
もう一度authアクションを見てみましょう。

(re-frame/reg-event-fx
  :auth
  [(re-frame/inject-cofx :token)]
  (fn [{:keys [token]} _]
    {:http-xhrio (-> {:method          :get
                      :uri             "/auth"
                      :on-success      [:auth-success]
                      :on-failure      [:auth-error]}
                     wrap-default-http
                     (wrap-token-http token))}))

この関数の3行目、inject-cofxがそれに当たります。(coはcoeffect:副作用)
外界情報入手用の関数 token を使用して、外界(localStorage)からtokenをこの世界(db)に注入しています。(http headerにtokenをセットしています)

関数 tokenはこんな感じです。localStorageから値を引っ張って来てます。

(re-frame/reg-cofx
  :token
  (fn [coeffects _]
    (assoc coeffects :token (.getItem (.-localStorage js/window) :token))))

と、以上loginの一連の流れとソースを見て来ました。

こう言ったように、re-frameのイベントはdbの内外を非常に意識させるAPIとなっています。

処理を細かく分解して書いていくのはなかなか大変だったりもしますが、こうやって一つ一つが小さくなったものを見るととても追いやすいと感じます。
なにより、こうやって書かざるを得ないので、表現がぶれることが少ないと感じます。

まとめ

re-frameにおけるdispatch(イベント)における副作用の有無について今回は見ていきました。

シリーズもので恐縮ですが、次回はサーバサイドについてざっくりとした解説をしたいと思います。
そしてスマホアプリへ・・


リサーチ・アンド・イノベーションでは、Clojureに限らず技術に興味のあるエンジニアを募集しています。 ご応募お待ちしております。

参考URL