なんか随分前に書きかけのままで、そのまま放置されていたので思い切って公開します。
ただ、不完全かつ古い記事なので参考にする際には注意してください。
ClojureScriptのRedux+Reactライクなフレームワークre-frameの入門記事です。
対象とする読者
下記の知識があることを前提にしています。
- Clojure
- 簡単なReactの知識
re-frameとは何か?
Mike Thompson氏が作った、SPA構築などに使えるフレームワークです。
ReactのラッパーであるreagentをUIなどのベースに、Redux風味な一方向なデータフローフレームワークを追加したものです。
…といっても、まったくもって部外者にとって、わかりづらいし、僕もReduxよくわかってないし、簡単なre-frameで作られたBMI計算機を例に説明します。
体重と身長を入力すると You BMI is XX.
が更新されるという、とてもシンプルなBMI計算機です。
これが、どういうふうにre-frameで動作するかというと、下記の図をご覧ください。
1〜3の順でデータがフローしていき、4にてビューを作ったのち、5などのイベントで7までにかけて1にまた差し掛かるといったグルグルとした図です。
1〜6の順に詳細説明します。
- データフローの起点。アプリの状態となるapp-dbと呼ばれるアプリのデータ置き場です。
- アプリの可変部となるデータはほぼここに集結していると思ってください。
- 今回であれば、コアデータは体重と身長なので、ここに入ってます。
- BMIは計算結果 (
= 体重 / (身長 * 身長)
) なので入れていません。
- dbを参照して
weight
、height
が計算されています。(計算というより、ただの値の参照ですが…) さらに計算されたweight
、height
を参照してbmi
が計算されています。 - これら上記の計算の値を用いてビューが構築されています。
- ここでも、計算の値はアトムとして提供されるので
@
が必要です。 - ビューはClojureのベクタやマップをHTMLのタグや属性のように書いて定義します。 (Hiccupスタイル:
[:HTMLタグ {:属性名 属性値 ...} タグの中身]
)- 構造さえ見れば、ほぼHTMLと同文法ですが、React DOMが定義しているHTMLライクなDSLと同文法といったほうが正確でしょう。なので、本物のHTMLとはちょっとかけ離れた部分もあります。
- ここでも、計算の値はアトムとして提供されるので
- 構築されたビューは実際に画面上に表示されます。
- 上記、1〜4のプロセスで、app-dbの内容がHTMLに変換されることがわかります。
- ということで、ここで一区切りです。
- ただし、それだけではただのHTMLを表示するだけのアプリになってしまうので、app-dbの中身を変える何かが必要です。(↓というのが5以降)
- ユーザの入力などにより、ビューに書いた
:on-change
ハンドラからハンドラ呼び出しが発火しています。- イベント発火タイミングは別にユーザの入力にかぎらず、タイマの時間が来た、Ajaxの結果が返ってきたなど色々あるでしょう。
- ハンドラ内で次の新しいアプリの状態となるapp-dbを生成しています。
- 具体的には
db
とハンドラの値
2つを引数に取って、新しいapp-db
を返す関数をイベントハンドラとして実行します。 - たとえば、
app-db
を新しく作らず、既存のものを書き換えればいいじゃないかと思いがちですが、Clojureはイミュータブルな言語なので、値の書き換えという概念がありません。値まるごと入れ替えのみ可能です。
- 具体的には
- 新たなapp-dbは無事、次のapp-dbとして置き換わり、また 1〜4 のプロセスを経て、新しいapp-dbの値に基づくビューが構築されます。
上記のプロセスを総括すると、
- 何らかのイベント契機で
app-db
を書き換われば - ビューが全部その通りに書き換わる
便利な仕組みを用意しているのがre-frameというフレームワークです。
かんたん用語整理
- React: JSのUIライブラリ。HTML状のDSLを書くことにより、HTMLや独自タグを表示することができる。
- Redux: よくReactと組み合わせて使われるフレームワーク。
- ClojureScript: JSに変換可能なClojureのサブセット。
- reagent: ClojureScriptのUIフレームワークの一つ。ReactをClojureっぽい風味にラップしている。
- re-frame: 本記事で扱っているClojureScriptのフレームワーク。
- app-db: アプリの内部状態
- subs: app-dbから派生している副次計算処理
利点:なぜClojureScriptやre-frameを使うのか?
jQueryよりビューの管理が簡単
見ての通り、app-dbというモデルからビューをまっすぐひねり出すだけなので、要素をたどって&くっつけたり必要があるjQueryより非常に明快です。
これならアプリが巨大になってもどうにか対処できそうです。
MVCフレームワークより楽ちん
モデルの変更をビューに反映できるMVCフレームワークも一時期流行っていましたが、モデルの○○の値が変わったから、ビューの☓☓を書き換えるという手続きはやっぱり面倒です。
イミュータブル
上記のメリットだけだと、JSで頑張ればいいじゃんと思われそうなので、ClojureScript/re-frame特有のメリットも述べていきます。
ClojureScriptは、Clojureなので、値(数値、文字列、セット、マップ、リストなど)全てが不変です。
内部構造を熟知してよほど邪悪なことをしない限り、一度作った値は何をしても書き換わることはありません。
app-dbの中身を書き換えようとしても書き換えることはできず、新しい値に置き換えるという手順が言語レベルで保証されてしまいます。
この言語レベルで値が不変という性質は言葉にされない面で、かなり思考を簡略化してくれます。
一般的にJSで記述されるReduxでは、値のイミュータビリティは頑張って確保するようです。あんまり詳しくないのでそれがどれぐらい面倒かは知りませんが、ClojureScriptでは何もしなくても値が全部不変なのは気楽です。
(とはいいつつ、反面あらゆる値が書き換えられないのは、既存の言語経験者にとって、もどかしさという面でも大きく代償があります。)
idiomatic: JSXとかいらない
僕が最初このJSXを見た時、戦慄してしまいました。
var Hello = React.createClass({
render: function() {
return (
<div>Hello {this.props.name}</div>
);
}
})
「見慣れない不純物が入ってるゥ〜〜〜!」
記法を簡単にするためとはいえ、JSにHTMLの形をしたエイリアンモンスターが入っていて拒否反応を起こしたものです。
一方 re-frame(というよりreagent)では、言語から飛び出さずとも自然に同じものを表現可能です。
(defn Hello [name]
(let []
(fn [name]
[:div "Hello " name])))
なお、ClojureScript自体がJS世界で不自然な記法そのものという指摘は受け付けておりません。
idiomatic: サーバサイドとクライアントサイドを同じ言語で構築可能
サーバサイドでClojureでWeb APIを構築、クライアントサイドでClojureScriptでUIを構築するといったことが可能です。
文法も似通っており、工夫すれば両者でコードのシェアも可能です。
マクロ
ClojureScriptはClojureで書いたマクロを用いることができます。
ClojureScriptのコンパイル時限定ですが、Clojureによる、JVMの能力をフルに用いた計算が可能です。
普通にマクロするのもよし、複雑な外部ファイル(仕様書とか)をインクルードするもよし、インターネットを参照してソース吐き出すもよし、という感じで普通のJSより夢が多めになってます。
REPL環境
ClojureScriptにはfigwheelといったREPL環境が揃っており、Emacsから直接ブラウザに繋いでファイル内の式をブラウザ上で評価させたり、アプリを自動リロードさせて漸次的に開発するなどが可能になっています。
これは非常に強力です。
REPLでいろんな式を評価させつつ、タイミングごとファイルを保存して、保存ののち自動的に画面に変更が反映されていることを直接確認しながら作業できます。
特にre-frameを用いている場合はアプリの状態がどういうふうにビューになるのか静かに見ながら作業可能です。
欠点:苦しみどころ
書き出すと止まらないのでいくつか…
JavaScriptではない
ClojureScriptは最終的にJavaScriptになりますが、JSの世界前提のツールやライブラリを用いるのがやや面倒です。
jQuery("#foo-bar").baz()
というかわいいjQueryコードですら、
(doto (js/jQuery "#foo-bar")
(.baz))
とやや面倒なものになります。 (JS世界のグローバルオブジェクトの下にぶら下がっているものは、js/
というプリフィックスが必要。)
その他、JSで配列だと思っていたものがClojureScriptではループを回せないとか、JS<=>Clojureではデータの互換性がないから変換関数かませる必要があるとか、CommonJSがないとか、結局は外でJS関連のWebpack回す羽目になってるとか、いろいろJSではない故の苦しみどころは出てきます。
もうこれはJSでしか書けないと思ったら、ClojureScriptで書く必要もないとも思います。
結局はjQueryのお世話になることはある
Reactの欠点といったほうがいいかもしれませんが、既存のUIライブラリ、たとえばMaterialize、こういったものはjQueryが随所随所にコンポーネントの初期化のために呼ばれる必要があったりします。
// Call initialization function after its mount.
$(".select-box").material_select();
まあ、そんなもん使うなという話ですが、これに限らず既存のものを組み込もうとするとこんなもんです。
既存のjQueryの資産は大きいですし、そもそも自分で何かReactの部品を作る時もjQueryに頼ることもあるでしょう。
ということで、別にReactを使ったからといってjQueryと一切縁が切れるわけではないです。
(スパゲッティを生み出すようなjQueryコードとは縁は切れますがね。)
変数の型は(基本的に)ない
TypeScriptのようなものではない。
日本語の情報が少ない
No English No 人権。現状、英語が読めないと割と死ねるでしょう。
そして軽く読めたとしても、雨後の筍のようにライブラリがニョキニョキ生えてきて何使ったら正解なのかは誰も教えてくれないっていうか誰か教えて…。
ちなみに作者はSlackにいますので、話し合うことも可能です。
ClojureScriptは劣化Clojureである
ClojureScriptにはClojureの機能の一部しかありません。
…と書いたものの、これについては、はっきりいって全然気にならないです。
Clojureから移植できない・されないような難しい機能はそもそも使わないしねえ〜。
主流の開発環境であるところのEmacsが小難しい
Emacsが訓練と慣れが必要なエディタなのは間違いありません。
悪名高きセットアップに関しては、Spacemacsで開発環境セットアップすれば割と楽です。
Emacs以外の編集環境の選択肢もありますが、個人的にあまり詳しくないです。(Emacsが使えるならEmacsをおすすめします)
大規模のやり方が分からない
TODOアプリみたいなしょうもないものではなく、複数人で複数画面の大作SPAを構築するうまいやり方を知っている人は少ないと思います。
結局、今のところ自分で適当にルールと足りない部品作ってやっていくしかありません。
ほか
- そもそもSPAという題材そのものが難しいと思っています。
- あとはClojureScript全般、ファイルが巨大になりがちです。Advanced compilation などしないとサイズセンシティブな環境では辛いでしょう。