ClojureScriptでReactチュートリアルやってみた (1)

# はじめに

※9/14 頂いたコメントをもとに更新

諸事情によりClojureScriptに入門する事になりましたので、こちらの記事
https://qiita.com/lagenorhynque/items/7c049f3c3b967ee777ac
を参考に馴染み深いReactチュートリアルに挑戦してみました。
ほとんど個人的な備忘録なのでかなり適当です。

まずは、[公式チュートリアルの「Declaring a Winner」の章](https://reactjs.org/tutorial/tutorial.html#declaring-a-winner)まで進めてみました。

全ソースは[こちら](
https://github.com/onishi024/react-tutorial-cljs/blob/16a1feb0af83f3940e17357a3baf148567654289/src/react_tutorial/core.cljs)。

# 1. クラス型コンポーネントの基本形

- 基本形

```cljs
(defn コンポーネント名 []
  (let [state (reagent.core/atom {:キー 値})]
     fn []
       (letfn
         [(ローカル関数)]
           [Hiccup風のDSL]))
```

- 実装

```cljs
(defn square [{:keys [on-click value]}]
  [:button.square
    {:on-click #(on-click)}
    value])

(defn calculate-winner [squares]
  (let [lines (vec [[0, 1, 2]
                    [3, 4, 5]
                    [6, 7, 8]
                    [0, 3, 6]
                    [1, 4, 7]
                    [2, 5, 8]
                    [0, 4, 8]
                    [2, 4, 6]])]
    (reduce (fn [_ [a b c]]
      (if (and (not= (squares a) "")
               (= (squares a) (squares b))
               (= (squares b) (squares c)))
        (reduced (squares a))
        nil))
      nil
      lines)))

(defn board []
  (let [state (r/atom {:squares (vec (repeat 9 ""))
                       :x-is-next? true})]
  (fn []
    (letfn
      [(handle-click [i]
        (let [x-is-next? (get @state [:x-is-next?])
              squares (get @state :squares)]
          (when (and (= (calculate-winner squares) nil) (= (squares i) ""))
            (swap! state assoc-in [:squares i] (if x-is-next? "X" "O"))
            (swap! state assoc [:x-is-next?] (not x-is-next?)))))
       (render-square [i]
        [square {
          :value (get-in @state [:squares i])
          :on-click #(handle-click i)
        }])]
        (let [status (if (= (calculate-winner (get @state :squares)) nil)
                         (str "Next player: " (if (get @state [:x-is-next?]) "X" "O"))
                         (str "Winner: " (calculate-winner (get @state :squares))))]
          [:div
            [:div.status status]
            [:div.board-row
            (render-square 0)
            (render-square 1)
            (render-square 2)]
            [:div.board-row
            (render-square 3)
            (render-square 4)
            (render-square 5)]
            [:div.board-row
            (render-square 6)
            (render-square 7)
            (render-square 8)]])))))
```

う〜ん、見慣れない記法ばかりで圧倒されますね。
各要素の意味についてひとつひとつ噛み砕いていきましょう。

## 1.1. HicCup風のDSL

こういう感じ
https://github.com/weavejester/hiccup/wiki/Syntax
でJSX表現をLisp的表現に置き換えていく。**Hiccupが何なのか分かってないですが。**

慣れるとすごく書きやすそう。

```cljs
[Hiccup風のDSL]
```

## 1.2. ローカル関数

`letfn`を用いてローカル関数とそのスコープを定義する事ができます。
ローカル関数が複数ある場合は単純に`[]`内に列挙していきます。

```cljs
(letfn
  [(ローカル関数)]
    [Hiccup風のDSL])
```

## 1.3. ローカル変数

`let`を用いてローカル変数とそのスコープを定義する事ができます。
適用先のスコープは関数である必要があるらしいので、無名関数`(fn [])`を設定しています。

```cljs
(let [state (reagent.core/atom {:キー 値})]
  fn []
    (letfn
      [(ローカル関数)]
        [Hiccup風のDSL])
```

ここではローカル変数とはstateの事です。
ClojureScriptは純粋なイミュータブルなので、一回設定した値を変更する事は基本的には認められません。

従って、`reagent.core/atom`を使って変更可能なプロパティを設定してあげます。

## 1.4. グローバル関数

コンポーネントクラスは`defn`を用いて名前空間内のグローバル関数の形で記述します。

```cljs
(defn コンポーネント名 []
  (let [state (reagent.core/atom {:キー 値})]
     fn []
       (letfn
         [(ローカル関数)]
           [Hiccup風のDSL])
```

ふう、基本形ができあがりました。

# 2. ClojureScriptコーディングあれこれ
単純にClojureScriptとして学んだ事を書いていきます。

## 2.1. しんどいstate更新

イミュータブルなので「配列のi番目を更新したいな〜`arr[i] = 2`」みたいな安易な記述は許されません。
`@`で`deref`したり`reset!`したり`swap!`するらしいです。

この記事が参考になりました。
https://gist.github.com/kohyama/6076544#atom

`@` or `deref` : referenceを参照する
`reset!` : 上書き
`swap!` : 関数を適用して再代入

`reset!`や`swap!`で変更できそうですね。実装を読み解いて行きましょう。

```cljs
(swap! state assoc-in [:squares i] (if x-is-next? "X" "O"))
```

`assoc-in`はオブジェクト(`state`)に対して探索条件(`[:squares i]`)で更新箇所を探索して更新値(`(if x-is-next? "X" "O")`)を設定します。
`assoc-in`探索条件を列挙して次から次へとネストを潜ることができるので、
- `:squares`で`state`内の`squares`要素を探索して、
- `i`で`squares`リストの`i`番目の要素を探索しています。

こういったオブジェクト探索は実プロダクトでは頻発する、かつコードが冗長化しがちですが、かなりスマートに書けますね。この手の配列操作(`map`とか`reduce`とか)はJavaScript ES6にも採用され始めていますが、やっぱりLispが始祖なんですかねえ(徐々に信徒の目に変わっていく)。この辺を使いこなせれば気持ちよくなっていけそうです。

## 2.2. しんどいstate参照

```cljs
[square {
  :value (get-in @state [:squares i])
  :on-click #(handle-click i)}]
```

- value

`value={this.state.squares[i]}`を書きたい

`assoc-in`のように`get-in`で探索条件設定して`state`を探索して参照します。
 - `:squares`で`state`内の`squares`要素を探索して、
 - `i`で`squares`リストの`i`番目の要素を探索しています。

- on-click

`onClick={() => this.handleClick(i)}`を書きたい

また、`#`は無名関数の表現で、コールバックを渡したいときに使っているみたいです。

## 2.2. Collectionのデータ型

```cljs
{:squares (vec (repeat 9 ""))}
```

`repeat`で`""`が９個格納された配列のようなもの(遅延シーケンス)を生成しますが、どうやらJavaScriptにおける配列と型が違うというか、associativeなデータ型にしないと`assoc-in`などで操作ができないため、`vec`でベクターにしています。

データ型についてはこちらを参照。まだ慣れていないので繰り返し見ていこうと思います。
http://freak-da.hatenablog.com/entry/2015/08/05/222224

## 2.3. console.log

```cljs
(js/console.log (pr-str value))
```

やっぱりデバッグでは`console.log`のお世話になるみたいです。JavaScriptの基本ライブラリにアクセスするようにはこのようにすればいいみたいです。

## 2.4. reduce, reduced
Vanilla JS

```js
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
```

ClojureScript

```cljs
    (reduce (fn [_ [a b c]]
      (if (and (not= (squares a) "")
               (= (squares a) (squares b))
               (= (squares b) (squares c)))
        (reduced (squares a))
        nil))
      nil
      lines)))
```
cljsで`for`のような安易な配列操作は好まれない(?)ようです。`reduce`等を使ってスマートに操作しましょう。

`reduce`は第1引数に関数、第2引数に初期値、第3引数にコレクションを設定します。第３引数のコレクションから1つずつ要素を取り出し、第1引数の関数で処理していきます。第1引数の関数は、第1引数に前回の処理結果、第2引数にコレクションから取り出した値を引き受けます。0番目のコレクション要素の処理のとき、前回の処理結果は存在しないため第2引数の初期値を使用します。

この例では、第3引数のlinesをひとつひとつ処理するのですが、第1引数の関数の第１引数は`_`となっております。これはUnused Argumentといって、使用しない引数に対して使います。こうする事で、単純にコレクションから要素をひとつひとつ取り出して検証する処理を記述できます。

また、`reduce`ですが、第１引数を返却値として`reduce`処理をbreakします。
こうする事で、条件に該当する要素が見つかったら処理を打ち切ることができます。



#3. 知識というか概念的なところ

##3.1. イミュータブルって
すごいいい記事見つけたと思ったらshinpeiさんだった
https://nekogata.hatenablog.com/entry/2013/06/15/013752

# 続き
[こちら](https://qiita.com/onishi024/items/ab6fb256251ef74011e8)