Reactのドキュメントではすでにこのチュートリアルは使用されていないのですが、そこら辺は気にせずに行きます。
CoffeeScript 2とは?
CoffeeScriptのメジャーアップデートです。2017年9月12日現在はbeta5ですが、期待の新人です。
v1.xから大きく変わったことは、コンパイル後のコードがES2015+になった事です。class等はclass等のまま出力されます。また、JSX表記がサポートされました。ただし、JSXをJavaScriptに変換するのでは無く、これまたJSXはJSXのまま出力されますので、別途Babel等での変換が必要です。
チュートリアルの書き換え
準備
いつものように https://github.com/reactjs/react-tutorial をcloneします。
今回はwebpackで一つにまとめたいと思います。理由としては、import
文を使いたかったとか、JSX表記でbabelを使う必要があるとか、色々です。ということで必要なモジュール類を入れていきます。各コマンドはyarnを使ってますので、yarnは予め入れておいてください。
yarn add react react-dom jquery remarkable
yarn add webpack babel-loader babel-core babel-preset-env babel-preset-react -D
yarn add coffeescript@next coffee-loader -D
coffee-loaderはCoffeeScript 2に対応済みです。
ファイルの用意
まずはwebpackの設定ファイルを用意します。せっかくなので、CoffeeScriptにしました。
path = require('path')
module.exports =
entry: './src/example.coffee'
output:
filename: 'example.js'
path: path.resolve(__dirname, 'public/scripts')
module:
rules: [
{
test: /\.coffee$/,
use: [
{
loader: 'babel-loader'
options:
presets: ['env', 'react']
}
'coffee-loader'
]
}
]
loaderの順番を変えるとうまくいきませんのでご注意ください。
次にindex.htmlの書き換えです。webpackで一つにまとめるため、読み込むJavaScriptのたった一つになります。
<!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" />
</head>
<body>
<div id="content"></div>
<script src="scripts/example.js"></script>
</body>
</html>
最後はCoffeeScriptのメインコードです。srcにディレクトリを作って、そちらに置くことにします。
###
* 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.
####
import React from 'react'
import ReactDOM from 'react-dom'
import $ from 'jquery'
import Remarkable from 'remarkable'
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
constructor: (props) ->
super props
@state = data: []
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()
componentDidMount: ->
@loadCommentsFromServer()
setInterval @loadCommentsFromServer, @props.pollInterval
render: ->
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={@state.data} />
<CommentForm onCommentSubmit={@handleCommentSubmit} />
</div>
CommentList = ({data}) ->
commentNodes = data.map (comment) ->
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
<div className="commentList">
{commentNodes}
</div>
class CommentForm extends React.Component
constructor: (props) ->
super props
@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'
)
コンパイル
後はコンパイルするだけです。
yarn run webpack
CoffeeScript -> ES2015+ with JSX -> JavaScript と変換されたexample.jsが生成されます。
解説
見ればわかるとおり、coffee-reactで書いたコードとほぼ同じです。coffee-reactで対応していた部分がCoffeeScript自体でJSXを認識し、Babelでさらに変換できるようになっただけとも言えます。
違いは下記の2点だけです。
- モジュールベース
webpackでまとめるためにモジュールベースにしています。つまりimport
文を使っていると言うことです。 -
super
の動作
CoffeeScript 2からsuper
とだけ書くことはできなくなりました。そのため、constructor()
ではprops
を引数に取り、super(props)
と渡す必要があります。
まとめ
いかがだったでしょうか?事例がほとんど見つからなくて、webpackでCoffeeScript + Babelの動作をさせるのに少し苦労しましたが、あとはいつも通りでした。JSXが本家で対応、そして、モジュールベースにできるようになった分、やりやすくなったのでは無いかと思っています。かといってCoffeeScriptに未来があるかどうかはまた別の問題かも知れませんが。