こんにちは。リサーチ・アンド・イノベーションの小川(J-ogawa)と申します。
弊社サービスの「あなたの日常を、もっといい日常に変える」そんなアプリCODEの
iPhoneアプリおよびサーバサイドの開発を担当してます。
本記事はシリーズ物で恐縮ですが、Clojureでブログやスマホアプリを作ってみようのコーナー、2回目となります。
前回 Clojureでブログやスマホアプリを作ってみる part1
基本方針
フレームワークにre-frameを使用しています。
re-frameはとてもイケてるフレームワークだと思います。
Elmもre-frameに影響を受けています。
解説記事予定
全4回に分けて解説を行いたいと思います。
前回は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のような処理を考えます。
この場合、処理の流れは以下となります。
/login
にhttp POSTして返却値としてaccess_tokenを得る- access_tokenをlocalStorageに保存
/auth
にaccess_tokenを投げて認証する- 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
- auth (副作用あり)
- store-token-localstrage (副作用あり)
- sign-error
- login-success (副作用あり)
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に限らず技術に興味のあるエンジニアを募集しています。 ご応募お待ちしております。