ClojureScriptでRedux(と時々Rum)
基本方針
reducer はマルチメソッドとして実装する
dispatcher の役割をマルチメソッドの呼び出し結果で状態変更することのみにすることができる
状態を受け取って状態を返すだけのマルチメソッドなのでテストしやすい
新しいアクションへの対応も楽
更新
2018-06-13
transform-state のデフォルトの挙動が nil を返してしまっていたので、状態をそのまま返すよう修正
超ざっくりまとめ
code:core.cljs
(ns foo.core
(:require-macros [cljs.core.async.macros :refer go-loop])) (defonce app-state (atom {:text "hello"}))
(defonce event-bus (async/chan))
;; Reducer
;; 状態とアクションを受け取って新しい状態を返すマルチメソッド
(defmethod transform-state :default state _ state) (defmethod transform-state :set-text
[state new-text]
(assoc state :text new-text))
;; アクションの待受
(go-loop []
(swap! app-state transform-state action)
(recur)))
;; イベントの発行
Integrant, Rum を使ったもう少しまともな例
ボタンを押しただけ増えていくだけの例
lein new figwheel foo で作ったテンプレートを前提
code:core.cljs
(ns foo.core
(:require-macros [cljs.core.async.macros :refer go-loop])) (defonce app (atom nil))
(def ^:private initial-state
{:num 1})
;; Component
;; ==================================================
(rum/defc num-block < rum/reactive
(let [{:keys num} (rum/react state)] [:div (str "num = " num)
;; Reducer
;; ==================================================
(defmethod transform-state :default state _ state) (defmethod transform-state :inc-num
(update state :num inc))
;; System
;; ==================================================
(defmethod ig/init-key :app/state
_
(atom initial-state))
(defmethod ig/halt-key! :app/state
state
(reset! state nil))
(defmethod ig/init-key :app.event/bus
_
(async/chan))
(defmethod ig/halt-key! :app.event/bus
ch
(async/close! ch))
(defmethod ig/init-key :app.event/dispatcher
(go-loop []
(swap! state transform-state action)
(recur))))
(defmethod ig/init-key :app/init
(js/console.log "start app")
(rum/mount (num-block state event-bus) node)
node))
(defmethod ig/halt-key! :app/init
node
(rum/unmount node))
(def app-config
{:app/state {}
:app.event/bus {}
:app.event/dispatcher
{:state (ig/ref :app/state)
:event-bus (ig/ref :app.event/bus)}
:app/init
{:state (ig/ref :app/state)
:event-bus (ig/ref :app.event/bus)}})
(defn start-app! []
(when-not @app
(reset! app (ig/init app-config))))
(defn stop-app! []
(when @app
(ig/halt! @app)
(reset! app nil)))
(defn on-js-reload []
(stop-app!)
(start-app!))
(start-app!)
参考