今回はcoffee-reactを使わずにCoffeeScript のみ で書いてみたいと思います。JSX記法は使わずに、どれだけ綺麗に書けるようになるかが焦点かと思っています。
React.jsチュートリアルをcoffee-reactで書いてみるは見ておいてください。左記記事で説明済みの事については省いていきます。
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="http://coffeescript.org/extras/coffee-script.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/coffeescript" src="scripts/example.coffee"></script>
<script type="text/coffeescript">
# 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>
coffee-react版との違いは、トランスパイラが.../coffee-script.js
となって、MIME Typeがtext/coffeescript
、拡張子が.coffee
となっているだけです。特に説明は不要ですね。
example.jsをexample.coffeeに置き換える
scripts/example.js
を下記のscripts/example.coffee
に置き換えます。コードでやってることは全く一緒です。
###
* 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.
###
Comment = ({author, children}) ->
rawMarkup = __html: (new Remarkable).render children.toString()
React.DOM.div className: "comment",
React.DOM.h2 className: "commentAuthor",
author
React.DOM.span dangerouslySetInnerHTML: rawMarkup
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: ->
React.DOM.div className: "commentBox",
React.DOM.h1 "Comments"
React.createElement CommentList, data: @state.data
React.createElement CommentForm, onCommentSubmit: @handleCommentSubmit
CommentList = ({data}) ->
React.DOM.div className: "commentList",
data.map (comment) ->
React.createElement Comment, author: comment.author, key: comment.id,
comment.text
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: ->
React.DOM.form className: "commentForm", onSubmit: @handleSubmit,
React.DOM.input
type: "text"
placeholder: "Your name"
value: @state.author
onChange: @handleAuthorChange
React.DOM.input
type: "text"
placeholder: "Say something..."
value: @state.text
onChange: @handleTextChange
React.DOM.input type: "submit", value: "Post"
ReactDOM.render(
React.createElement CommentBox, url: "/api/comments", pollInterval: 2000
document.getElementById 'content'
)
解説
classにする、=>
を使うとかはcoffee-reactと同じですが、変わったところを説明していきます。
React.createElement
とReact.DOM
JSXを使わない場合、React.createElement
やReact.DOM
等を使う必要があります。生のJavaScirptではとても冗長な書き方になってしまい、あまり効率的とは言えません。しかし、CoffeeScriptではJadeやSlimのようにスマートに書くことができます。少々書き方にコツがいりますが、JSX記法を使わなくても十分と言えるでしょう。
何度もReact...と書くのが嫌という場合は、React.jsチュートリアルをLiveScriptで書いてみるを参考にしてみてください。CoffeeScriptでもできないことはありません。
ステートレス関数コンポーネント
状態を持たないComment
とCommentList
は0.14系から使用できるようになったステートレス関数コンポーネントにしてみました。今回は混ざってしまっているので綺麗とは言い難いですが、全体的にステートレスなコンポーネントだけで作成するのであれば、とても有効な記法です。とくにCoffeeScriptは最後の式を自動的にreturnするという特徴があるため、生のJavaScriptに比べて書きやすくなっています。
まとめ
CoffeeScriptであればJSX記法を使わなくても大丈夫という感じです。とくに、CommentList
でdata.map ...
の部分のようにそのまま書けることは利点です。JSX記法では{}内しかJavaScriptのコードとならないため、データの配列からElementの配列を作る場合は別途変数に入れる必要がありますが、JSX記法で無い場合は普通にその場で書いたものが引数と入れることができます。
ES6やTypeScriptではどうしてもJSX記法でないと書きにくいReactですが、CoffeeScriptであればそれも不要と言うことで、CoffeeScriptの良いところがよくわかる例の一つではないでしょうか?