LoginSignup
8

More than 5 years have passed since last update.

posted at

updated at

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

前回の続きで、React Tutorialのサンプルコードを今度はLiveScriptで書き直してみました。やり方は前回と一緒なので、まだ見てない人はそっちも見てください。

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.jsprelude-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というものがあるのですが、今回はつかっていません。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の生成、そして、渡された引数を渡して実行すればいいのです。

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

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

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に関数型プログラミングの要素を追加した、もう、なんでもありな言語に仕上がっていますので、すごくすっきりとしたコーディングが(たぶん)可能です。これを機に皆さんもLiveScriptを触ってみてください。次回はFlux Tutorial - Todo Listあたりに挑戦してみようかなと思っています。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8