ClojureScript + Reagent で 3目並べ (tic tac toe)
React チュートリアルのお題 3目並べを ClojureScript + Reagent で書いてみた。
Reagent とは
ClojureScript から React を利用しやすくするライブラリで、公式のサンプル をざっとみてもらうと、想像しやすいかと思います。
コンポーネントは React のように jsx ではなく、
[:h2.subtitle "Title"]
のように書きます。
これは Clojure のHTML出力用ライブラリ hiccup の記法で、「HTMLってS式で表現できるじゃん」という Lisper 的な発想から出てきた記法だと思います。Clojure だけではなく、 おそらく Common Lisp/Scheme にも、類似のライブラリがあるはずです。
デモページ
React チュートリアルに示されている完成サンプルと異なる点があります。
ステータスの表示
引き分けと勝者を表示する
履歴の機能
履歴を辿ると、その履歴の前のものが全てが消えてしまう
はまったところ
データが更新されたら、まるっとデータが消される罠
(let [data (new-data)] [:div.game-info この中で data を使う])
というようにコンポーネントの中身を書いていたら、このコンポーネントが更新されたときに、データも空になってしまうという、自分の間抜けな実装に悩まされました。
(new-data)
を引数としてコンポーネントに渡す、もしくは defonce
でグローバルな変数として持つという形にすれば解決できます。
データ構造の選択について
初めは Map の中に atom を入れる実装にしていました。
{ :board [(reagent.core/atom "") (reagent.core/atom "") ...] :status (reagent/atom :in-play) ... }
ただ、先駆者の実装をみてみると、
(reagent.core/atom { :board ["" ""...] :status :in-play ... })
のように Map 全体を atom としていて、そうすると、値の参照や更新が書きやすくなりました。
具体的にいえば、参照の時は deref が減り、更新の時は (swap! atomic-data assoc :key "SOME VALUE")
というパターンに統合されました。ところどころ、reset! はありますが。
結論は、今回の3目並べだと、全体的に atom としても良いと思います。
ただ、ある関数はこの値だけを参照・更新して、他の関数は別の値だけ参照・更新する、というような場合だと、全体的に atom としてしまうのは無駄な気がします。
また、そういう場合だと「なんで 1つのMap に詰め込んでいるんだ!」とデータ構造警察が訪ねてきそうです。
環境づくり
最後に、環境設定についても触れておきます。
1. とりあえず leiningen をインストール
2. プロジェクトの作成
プロジェクトのスケルトンを作ってくれるので、あまり考えずにスタートできるのが、魅力的です。今回バックエンドは不要だったので、フロントエンドのみのスケルトンを使いました。
https://github.com/reagent-project/reagent-frontend-template
$ lein new reagent-frontend <APPNAME> $ cd <APPNAME> $ lein figwheel ... ... あとはコーディングを頑張る
figwheel は ClojureScript の高機能ビルドツールです。
開発サーバをたててくれたり、デバッガ + オートローダ付きだったり、ブラウザ上のアプリとつながった REPL を利用できたりと、多機能過ぎます。
何が内部で起こっているのか、何を使っているのか、など気になるので、lein/project.clj のお作法についても、知っておく必要があります。
3. わからなかったら、Clojurescript のチートシートを参考に
常時タブに開いておきました: http://cljs.info/cheatsheet/
次は何をしようか。
面白い記事があるので、ClojureScript でブラウザ上で動くdiff を実装してみたいと思います。