読者です 読者をやめる 読者になる 読者になる

Arantium Maestum

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

Clojure Web Development勉強 - ReactチュートリアルをReagentでやってみる(その1)

Clojure Clojurescript Web React Reagent

Reactの公式ページに基本概念などの説明のためのチュートリアルがある:

facebook.github.io

これをClojureScript/Reagentでやっていきたい。

最終的な成果物は簡単な○×ゲーム

HTMLやCSS、そしてReact Componentの大枠はすでに出来上がっていて、チュートリアルではこれに肉付けしていく流れとなっている。

codepen.io

今回とりあえずこの枠組みだけClojureScript/Reagentで用意した

CSSだけチュートリアルのサンプルコードの物をそのまま写して、それ以外はClojureScript的にアレンジしてある。HTMLはチュートリアルのものがなんだかJavaScriptでごちゃごちゃしていたのだが、今のところはあらかた無視してCSSJavaScriptのインポートとReagent用のDivの設定だけ。project.cljはいつもの。

さて、肝はcore.cljsである。その中にSquare、BoardそしてGameという三つのReactコンポーネントが定義されている。ClojureScriptとJavaScriptを対比させながら見ていこう。

Squareコンポーネント

SquareはこのReact Appの最小コンポーネントで、クリックされると○か×を表示する小さな四角い枠である。

ClojureScript:

(defn Square []
  [:button.square])

JavaScript:

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]]])))

JavaScript:

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と関係ないので実際に使われる段階になって解説する。

次回以降チュートリアル通りに進めていく。

今回のソース:

gist.github.com