Arantium Maestum

プログラミング、囲碁、読書の話題

Clojure Web Development勉強 - Reagent(その2)Reagent Atom

さて、Reagentの二大要素の二つ目であるReagent Atomについて。

Reagentでは、コンポーネントの可変な状態を保持するために特殊なAtomを用意している。

と言っても概念・機能は通常のClojureAtomとほとんど同じで、Immutableなデータ構造へのポインタのようなものである。

Clojure Atomと同じInterfaceを持ち、swap!やreset!などで新しいデータを指すようにでき、その指し示すデータを@でderefできる。

例えば:

(def a (r/atom 0))
(js/console.log @a)

(reset! a 5)
(js/console.log @a)

(swap! a inc)
(js/console.log @a)

これでブラウザのログに0と5と6が出力される。

ではReagent AtomClojure Atomとの違いは何か。公式のReagentチュートリアルによると:

The easiest way to manage state in Reagent is to use Reagent’s own version of atom. It works exactly like the one in clojure.core, except that it keeps track of every time it is deref’ed. Any component that uses an atom is automagically re-rendered when its value changes.

つまり、あるReagent Atomを参照しているコンポーネントはそのAtomの値が変わるたびに自動的に更新され、常に最新の値を反映しているようになる。

例:

(def clicks (r/atom 0))

(defn clicker []
  [:div
    [:button {:on-click #(swap! clicks inc)} "Click me"]
    [:p "Click count: " @clicks]])

最初のbutton要素はclickイベントに反応して#(swap! clicks inc)が走るようになっており、clicksのその時その時の値が、p要素に表示されるようになっている。

Reagent Atomの中身はもちろんVectorやMapであっても良い:

(def clicks 
  (r/atom {:John 0 :James 0}))

(defn clicker [name-key]
  [:div
    [:button {:on-click #(swap! clicks update name-key inc)} name-key]
    [:p "Click count: " (get @clicks name-key)]])

(defn my-app []
  [:div
    [clicker :John]
    [clicker :James]])

clickerコンポーネント関数がkeyを受け取り、そのkeyに応じてclick r/atomのmapに入っている正しい値を表示したり更新したりしている。:button内での名前の表示は、Reagentの0.6.0-rc以降、keywordを直接コンポーネントの引数としていれると文字列として表示してくれるようになったのを利用している。

次回はこのr/atomを使ったコンポーネントの状態管理のパターンをいくつか紹介したい。