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


_【2016年6月12日更新】0.15.0系になっている64a6dfca72aca4d13ab4cbf23c50eba8b9fabe8a(2016-06-08 23:09)からのフォーク版に差し替えました。それに合わせて古い情報などは更新しています。_

[React Tutorial](https://facebook.github.io/react/docs/tutorial.html)のサンプルコードを[coffee-react](https://github.com/jsdf/coffee-react)で書き直してみました。英語が苦手って方は、優秀な翻訳[React.jsチュートリアル【日本語翻訳】](http://mae.chab.in/archives/2872)をみてみてください。

チュートリアルのコードは[github:reactjs/react-tutorial](https://github.com/reactjs/react-tutorial)から入手できます。まずはそれを入手してください。

## index.htmlを書き換える

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

```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>React Tutorial</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/15.0.1/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/remarkable/1.6.2/remarkable.min.js"></script>
    <script src="https://wzrd.in/standalone/coffee-react-browser"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/cjsx" src="scripts/example.cjsx"></script>
    <script type="text/cjsx">
      # To get started with this tutorial running your own code, simply remove
      # the script tag loading scripts/example.js and start writing code here.
    </script>
  </body>
</html>
```

変わった所は`.../browser.js`が`.../coffee-react-browser`になったところと、bodyにあるscriptタグの部分です。`.../coffee-react-browser`がCoffeeScript版JSX(CJSX)を変換するライブラリです。普通のJSXは不要なので、`.../browser.js`が削除されています。

つぎに、bodyのscriptタグですが、次の通りです。

* `text/babel` -> `text/cjsx` BabelによるES6なJSXからCJSXに変更しています。
* `scripts/example.js` -> `scripts/example.cjsx` 拡張子を変更しています。
* `//` -> `#` サンプルのコメント部分をCoffeeScriptの方式に変更します。

実際の本番はCJSXをcoffee-reactで普通のJavaScriptに変換しておくことを推奨します。なお、JSXの場合も普通のJavaScriptに変換しておくことが推奨されています。

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

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

```example.cjsx
###
 * 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.
###

class Comment extends React.Component
  rawMarkup: ->
    md = new Remarkable()
    rawMarkup = md.render @props.children.toString()
    { __html: rawMarkup }

  render: ->
    <div className="comment">
      <h2 className="commentAuthor">
        {@props.author}
      </h2>
      <span dangerouslySetInnerHTML={@rawMarkup()} />
    </div>

class CommentBox extends React.Component
  loadCommentsFromServer: =>
    $.ajax
      url: @props.url
      dataType: 'json'
      cache: false
      success: (data) =>
        @setState {data: data}
      error: (xhr, status, err) =>
        console.error @props.url, status, err.toString()

  handleCommentSubmit: (comment) =>
    comments = @state.data
    # Optimistically set an id on the new comment. It will be replaced by an
    # id generated by the server. In a production application you would likely
    # not use Date.now() for this and would have a more robust system in place.
    comment.id = Date.now()
    newComments = comments.concat [comment]
    @setState data: newComments
    $.ajax
      url: @props.url
      dataType: 'json'
      type: 'POST'
      data: comment
      success: (data) =>
        @setState {data: data}
      error: (xhr, status, err) =>
        @setState data: comments
        console.error @props.url, status, err.toString()

  constructor: ->
    super
    @state = data: []

  componentDidMount: ->
    @loadCommentsFromServer()
    setInterval @loadCommentsFromServer, @props.pollInterval

  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentList data={@state.data} />
      <CommentForm onCommentSubmit={@handleCommentSubmit} />
    </div>

class CommentList extends React.Component
  render: ->
    commentNodes = @props.data.map (comment) ->
      <Comment author={comment.author} key={comment.id}>
        {comment.text}
      </Comment>
    <div className="commentList">
      {commentNodes}
    </div>

class CommentForm extends React.Component
  constructor: ->
    super
    @state = author: '', text: ''

  handleAuthorChange: (e) =>
    @setState author: e.target.value

  handleTextChange: (e) =>
    @setState text: e.target.value

  handleSubmit: (e) =>
    e.preventDefault()
    author = @state.author.trim()
    text = @state.text.trim()
    return if !text or !author
    @props.onCommentSubmit author: author, text: text
    @setState author: '', text: ''

  render: ->
    <form className="commentForm" onSubmit={@handleSubmit}>
      <input
        type="text"
        placeholder="Your name"
        value={@state.author}
        onChange={@handleAuthorChange}
      />
      <input
        type="text"
        placeholder="Say something..."
        value={@state.text}
        onChange={@handleTextChange}
      />
      <input type="submit" value="Post" />
    </form>

ReactDOM.render(
  <CommentBox url="/api/comments" pollInterval={2000} />,
  document.getElementById('content')
)
```

どうですか？大分すっきりしたと思いませんか？さて、解説していきたいと思います。

### classベースなReact

React 0.13.0以降はclassとして書けるようになりました。`React.createClass(...)`と書くよりもすっきりと意味論が通った形で書けるようになっています。これはES6だけで無くCoffeeScriptやTypeScriptもサポートされており、これからの標準になると思います。

### 初期化は`constructor()`で

`state`の初期化は`getInitialState`ではなく`constructor()`で行います。他にも、`getDefaultProps`の代わりに`@defaultProps` **クラス変数**、`propTypes`も`@propTypes` **クラス変数**を使います。例外はこの三つだけで、`render`等はインスタンスメソッドとして定義し、`setState()`等もインスタンスメソッドとして定義されています。なお、プロパティを使えるようになるために、`constrauctor()`で最初に`super`(引数指定は不要)を呼び出しておく必要があります。

### ハンドルなメソッドはファットアロー`=>`を使え

`->`ではなく`=>`で定義が書いてある部分があるのがわかりますでしょうか？具体的には`loadCommentsFromServer()`と`handleCommentSubmit()`、`handleSubmit()`です。最初の一つはちょっと特殊ですけど、他の二つはイベント発生時に呼び出されます。しかし、呼び出し時の`@`(`this`)は呼び出し元になってしまい、そのままでは`@`が呼び出し先のインスタンスでは無くなってしまいます。そこでファットアロー`=>`の出番となっています。他にも`.bind(@)`をつけて束縛する等の方法もありますが、CoffeeScriptでは`=>`を使った方がすっきり書けます。

この方法は`$.ajax()`での`success`や`error`の処理でも使用しています。

## その他

classベースなReactについては[Reusable Components | React#ES6 Classes](https://facebook.github.io/react/docs/reusable-components.html#es6-classes)を参考にしてみてください。上のコードのライセンスはオリジナルと同じです。

書き終わった後に気付いたんですが、すでに書いている人がいました。[Reactの公式チュートリアルをCoffeeScriptで書いてみた - Qiita](http://qiita.com/notsunohito/items/4c6785d1d4866f493c8e)も見てみてください(ちょっとやり方違うので比較もできるかと)。

なお、上の記事ではエディタにmodeがなさそうっていってますが、Atomなら[language-cjsx](https://atom.io/packages/language-cjsx)が、Sublime Textなら[sublime-cjsx](https://github.com/Guidebook/sublime-cjsx)[^1]が既にあります。他のエディタは知らないです。

[^1]: 情報提供 thanks: @59naga さん