【2016年6月12日更新】0.15.0系になっている64a6dfca72aca4d13ab4cbf23c50eba8b9fabe8a(2016-06-08 23:09)からのフォーク版に差し替えました。それに合わせて古い情報などは更新しています。
React Tutorialのサンプルコードをcoffee-reactで書き直してみました。英語が苦手って方は、優秀な翻訳React.jsチュートリアル【日本語翻訳】をみてみてください。
チュートリアルのコードはgithub:reactjs/react-tutorialから入手できます。まずはそれを入手してください。
index.htmlを書き換える
pubilc/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
に置き換えます。コードでやってることは全く一緒です。
###
* 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を参考にしてみてください。上のコードのライセンスはオリジナルと同じです。
書き終わった後に気付いたんですが、すでに書いている人がいました。Reactの公式チュートリアルをCoffeeScriptで書いてみた - Qiitaも見てみてください(ちょっとやり方違うので比較もできるかと)。
なお、上の記事ではエディタにmodeがなさそうっていってますが、Atomならlanguage-cjsxが、Sublime Textならsublime-cjsx1が既にあります。他のエディタは知らないです。