Clojure Web Development勉強 - ReactチュートリアルをReagentでやってみる(その1)
Reactの公式ページに基本概念などの説明のためのチュートリアルがある:
これをClojureScript/Reagentでやっていきたい。
最終的な成果物は簡単な○×ゲーム。
HTMLやCSS、そしてReact Componentの大枠はすでに出来上がっていて、チュートリアルではこれに肉付けしていく流れとなっている。
今回とりあえずこの枠組みだけClojureScript/Reagentで用意した。
CSSだけチュートリアルのサンプルコードの物をそのまま写して、それ以外はClojureScript的にアレンジしてある。HTMLはチュートリアルのものがなんだかJavaScriptでごちゃごちゃしていたのだが、今のところはあらかた無視してCSSとJavaScriptのインポートとReagent用のDivの設定だけ。project.cljはいつもの。
さて、肝はcore.cljsである。その中にSquare、BoardそしてGameという三つのReactコンポーネントが定義されている。ClojureScriptとJavaScriptを対比させながら見ていこう。
Squareコンポーネント
SquareはこのReact Appの最小コンポーネントで、クリックされると○か×を表示する小さな四角い枠である。
ClojureScript:
(defn Square [] [:button.square])
class Square extends React.Component { render() { return ( <button className="square"> {/* TODO */} </button> ); } }
ミジカァァァいッ!説明不要!
というのも寂しいので少し解説すると、Squareコンポーネントはcssで定義されたsquare classのbutton要素である。今のところ引数なし。Reagentではclassは[:div.element-class]
、idは[:div#element-id]
と書ける。
特にこのような現状ではものすごくシンプルなコンポーネントについては、JavaScript/Reactに比べてボイラープレートが非常に少ない。というかこれ以上切り詰められないのではと思わせるほどの素晴らしいシグナル・ノイズ比。
Boardコンポーネント
次のプレーヤーが誰かを書いた:divとSquareコンポーネントを9つ表示するBoardコンポーネント。
ClojureScript:
(defn Board [] (let [renderSquare (fn [i] [Square]) status (r/atom "Next player: X")] (fn [] [:div [:div.status @status] [:div.board-row [renderSquare 0] [renderSquare 1] [renderSquare 2]] [:div.board-row [renderSquare 3] [renderSquare 4] [renderSquare 5]] [:div.board-row [renderSquare 6] [renderSquare 7] [renderSquare 8]]])))
class Board extends React.Component { renderSquare(i) { return <Square />; } render() { const status = 'Next player: X'; return ( <div> <div className="status">{status}</div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } }
コンポーネント固有のrenderSquare methodとstatusという文字列が存在する。ClojureScript版ではletを使ってRe-FrameのいうところのForm-2 Componentを作成し、クロージャでrenderSquareとstatusをコンポーネントに束縛している。
renderSquareは現在まったく無駄だが(Squareを直接呼び出せば良い)、チュートリアルを先読みすると、後々Squareに渡す引数の作成など複雑な処理が入る予定っぽい。面白いことに、ClojureScript版だとrenderSquare自体がReagent的にはForm-2 Component関数になっている(はず)。
そのおかげもあり、非常に統一された外見で何が起きているかある程度わかりやすいように感じる。JavaScriptのほうは本当にJavaScriptとHTMLの悪魔合体というかなんというか・・・ ただ、実際の処理に関してはClojureScript版のほうが「どの時点でどの処理が実行されるか」(特にlet bindingが再実行されてしまうかどうか)に気をつかわないといけないところはあるかもしれない。
微妙に[:div.board-row ...]
あたりを別コンポーネントに切り出したくなるが、とりあえず今は我慢・・・
Gameコンポーネント
Boardとゲーム情報をリストとして表示するdivを持ったGameコンポーネント
(defn Game [] [:div.game [:div.game-board [Board]] [:div.game-info [:div] [:ol]]])
class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{/* status */}</div> <ol>{/* TODO */}</ol> </div> </div> ); } }
あまり話すことがない。他の二つに比べるとJavaScript/ClojureScriptの対比もあまり際立っていない。
calculateWinner関数はReactと関係ないので実際に使われる段階になって解説する。
次回以降チュートリアル通りに進めていく。
今回のソース: