LoginSignup
6
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-09-12

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にしました。

webpack.config.coffee
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のたった一つになります。

public/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" />
  </head>
  <body>
    <div id="content"></div>
    <script src="scripts/example.js"></script>
  </body>
</html>

最後はCoffeeScriptのメインコードです。srcにディレクトリを作って、そちらに置くことにします。

src/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.
####

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に未来があるかどうかはまた別の問題かも知れませんが。

6
4
0

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
  3. You can use dark theme
What you can do with signing up
6
4