React.jsチュートリアルをLiveScriptで書いてみる


[前回](http://qiita.com/raccy/items/7c212f45cf8636e5927b)の続きで、[React Tutorial](https://facebook.github.io/react/docs/tutorial.html)のサンプルコードを今度は[LiveScript](http://livescript.net/)で書き直してみました。やり方は前回と一緒なので、まだ見てない人はそっちも見てください。

## index.htmlを書き換える

`pubilc/index.html`を次のように書き換えます。

```index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Hello React</title>
    <!-- Not present in the tutorial. Just for basic styling. -->
    <link rel="stylesheet" href="css/base.css" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.0/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/livescript/1.4.0/livescript-min.js"></script>
    <script src="http://www.preludels.com/prelude-browser-min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/ls" src="scripts/example.ls"></script>
    <script>
      require("LiveScript").go();
    </script>
  </body>
</html>
```

今回は`livescript-min.js`と`prelude-browser-min.js`が加わっています。また、MIMEタイプは`text/ls`となっています。最後のスクリプトコードはLiveScriptをJavaScriptに動的にコンパイルするのに必要です。

## example.jsをexample.lsに置き換える

`scripts/example.js`を下記の`scripts/example.ls`に置き換えます。コードでやってることは(たぶん)全く一緒です。

```example.ls
/**
 * This file provided by Facebook is for non-commercial testing and evaluation purposes only.
 * Facebook reserves all rights not expressly granted.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

require! 'prelude-ls': {Obj}

class Component extends React.Component implements React.DOM
    @element = ->
        React.create-factory(@) ...

class Comment extends Component
    render: ->
        @div class-name: \comment,
            @h2 class-name: \commentAuthor,
                @props.author
            @span dangerouslySetInnerHTML:
                __html: marked @props.children.to-string!, sanitize: true

class CommentBox extends Component
    (props) !->
        super ...
        @state = data: []

    access-comments: (settings) !->
        default-settings =
            url: @props.url
            data-type: \json
            success: (data) !~>
                @set-state data: data
            error: (xhr, status, err) !~>
                console.error @props.url, status, err.to-string!
        $.ajax default-settings with settings

    load-comments-from-server: !~>
        @access-comments cache: false

    handle-comment-submit: (comment) !~>
        @set-state data: @state.data ++ [comment], !->
            @access-comments type: \POST, data: comment

    component-did-mount: !->
        @load-comments-from-server!
        set-interval @load-comments-from-server, @props.poll-interval

    render: ->
        @div class-name: 'commentBox',
            @h1 null, 'Comments'
            CommentList.element data: @state.data
            CommentForm.element on-comment-submit: @handle-comment-submit

class CommentList extends Component
    render: ->
        @div class-name: \commentList,
            @props.data.map (comment, index) ->
                Comment.element author: comment.author, key: index,
                    comment.text

class CommentForm extends Component
    handle-submit: (e) !~>
        e.prevent-default!
        ref-nodes = @refs{author, text} |> Obj.map React.findDOMNode
        ref-vals = ref-nodes |> Obj.map (.value.trim!) |> Obj.compact
        if \author of ref-vals and \text of ref-vals
            @props.on-comment-submit ref-vals
            ref-nodes |> Obj.each -> it.value = ''

    render: ->
        @form class-name: \commentForm, on-submit: @handle-submit,
            @input type: \text, placeholder: 'Your name', ref: \author
            @input type: \text, placeholder: 'Say something...', ref: \text
            @input type: \submit, value: \Post

React.render CommentBox.element(url: 'comments.json', poll-interval: 2000),
    document.get-element-by-id \content
```

もう、なんとういうか、なにか別世界に来た感じです。さて、解説していきたいと思います。なお、classベースなどのcoffee-reactと被る部分は説明を省きます。

### <タグ>からの解放

JSXおよびJSX派生言語(coffee-react等)で一番の特徴はHTMLと同じタグ表記です。LiveScriptにも[react-livescript-jsx](https://github.com/wolfflow/react-livescript-jsx)というものがあるのですが、今回はつかっていません。SGMLからの伝統を引き継ぐこのタグ表記はHTMLだけしか知らない人にはいいかもしれませんが、いかんせん、冗長すぎて書くのがつらいです。HAML/Jade/Slimとかに慣れている人からすると、今更タグで書くのは苦痛です。

React.jsはJSXを使いたくない人にもちゃんとコーディングする方法を用意しています。新たにelementを作る方法は三つです。

* `React.createElement()`を使う。
* `Recat.createFactory()`を使う。
* `React.DOM.要素名()`を使う。(HTML要素のみ)

最後はHTML要素のみにしか使えませんが、どれを使ってもいいです。ただ、どれも書き方が冗長です。何度も何度も`React...`って書いていくのはつらいです。そこで、一工夫することにします。

#### HTML要素は`React.DOM`をmixin！

例えば、div要素は`React.DOM.div()`とすることで作ることが可能です。では、これをもっと簡単にスマートにするには？よくみると`div()`は`React.DOM`のクラスメソッドです。そう、だったらmixinしちゃえば、`@div()`とかけるようになるということです。LiveScriptではclass定義に`implements`キーワードでmixinが可能です。親クラスとは別に指定できますので、これを使いましょう。

#### ReactClassは`Recat.createFactory()`をクラスメソッドで実行！

残念ながらReactClassには`React.DOM`は使えません。同じ方法を取ろうにも、class定義時にあとどれだけReactClassを定義するかなんて知る方法はありません。同じ方法はあきらめて、簡単にFactoryを取得・実行するようにすればいいと考えました。各ReactClassで`elemente()`クラスメソッドを実装し、内部では`React.createFactory()`でFactoryの生成、そして、渡された引数を渡して実行すればいいのです。

#### まとめたものをクラスとして作って継承していく

こうして、上をまとめた最低限のクラスは下記になります。

```livescript
class Component extends React.Component implements React.DOM
    @element = ->
        React.create-factory(@) ...
```

コンポーネント作成するときは、`React.Component`の代わりに上の`Compoent`を継承するようにすると、`render()`で`@div()`や`Comment.element()`という表記ができるようになります。最終的には今回のコードのようなHAML/Jade/Slimっぽいコードで書くことができると言うことです。

なお、上の3行の部分はパブリックドメインですので、お使いになる場合はご自由に。

### その他

キャメルケースをチェインケースにしたり、パイプ`|>`で繋いだり、prelude.lsを使ったり、LiveScriptっぽくできそうな所はなるべくしてみて、DRYの原則に立つようにしてみました。でも、そもそものコードが短いので、あまり関数型プログラミングっぽくできなかったです。

## まとめ

いかがだったでしょうか？LiveScriptはCoffeeScriptをさらに強力にした[Coco](http://satyr.github.io/coco/)に関数型プログラミングの要素を追加した、もう、なんでもありな言語に仕上がっていますので、すごくすっきりとしたコーディングが(たぶん)可能です。これを機に皆さんもLiveScriptを触ってみてください。次回は[Flux Tutorial - Todo List](https://facebook.github.io/flux/docs/todo-list.html)あたりに挑戦してみようかなと思っています。