flux
es6
reactjs
webpack
babel

React v15.1.0 チュートリアルをReact + ES6(Babel) + Webpackで実装する

More than 1 year has passed since last update.

概要

React + ES6 + Webpackでチュートリアルを行ったのでES6バージョンのソースをメモ。
【本家】 https://facebook.github.io/react/docs/tutorial.html

ES6変換ソース一覧

webpack.config.js

/webpack.config.js
let path       = require('path');
let webpack    = require('webpack');

const PATHS = {
  src: path.join(__dirname, 'src'),
  www: path.join(__dirname, 'www')
};

module.exports = {
  entry: [
    `${PATHS.src}/App.jsx`
  ],
  output: {
    path: PATHS.www,
    filename: 'bundle.js'
  },
  resolve: {
    extensions: [
      '',
      '.js',
      '.jsx'
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({
      jQuery: 'jquery',
      $: 'jqeury'
    })
  ],
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './www',
    port: 3000,
    hot: false,
    inline: true,
    colors: true
  },
  module: {
    loaders: [
      // Babel
      {
        test: [
          /\.jsx$/,
          /\.js$/
        ],
        loaders: [
          'babel'
        ],
        exclude: /node_modules/
      },
      // Sass
      {
        test: [
          /\.scss$/
        ],
        loaders: [
          'style',
          'css',
          'sass'
        ]
      },
      // CSS & Bootstrap
      { test: /\.css$/, loader: 'style-loader!css-loader' },
      { test: /\.svg$/, loader: 'url-loader?mimetype=image/svg+xml' },
      { test: /\.woff$/, loader: 'url-loader?mimetype=application/font-woff' },
      { test: /\.woff2$/, loader: 'url-loader?mimetype=application/font-woff' },
      { test: /\.eot$/, loader: 'url-loader?mimetype=application/font-woff' },
      { test: /\.ttf$/, loader: 'url-loader?mimetype=application/font-woff' }
    ]
  }
};

.babelrc

/.babelrc

{
  "presets": [
    "react",
    "es2015"
  ]
}

App.jsx

/src/App.jsx

/*::::::::::::::::::::::::::::::::::
 JS
:::::::::::::::::::::::::::::::::::*/
import React from 'react';
import ReactDOM from 'react-dom';

/*::::::::::::::::::::::::::::::::::
 Components
:::::::::::::::::::::::::::::::::::*/
import CommentApp from './components/Comment/CommentApp';

/*::::::::::::::::::::::::::::::::::
 InitialDOM
:::::::::::::::::::::::::::::::::::*/
let mountNode = document.getElementById('mountNode');

/*::::::::::::::::::::::::::::::::::
 AppComponent Defined
:::::::::::::::::::::::::::::::::::*/

export default class App extends React.Component {
  render() {
    return (
      <div className="container-fluid">
        <CommentApp url="http://XXXXXXXXXX/dummy/comments.json" pollInterval={2000} />
      </div>
    );
  }
}

/*::::::::::::::::::::::::::::::::::
 ToDOMRendering
:::::::::::::::::::::::::::::::::::*/
ReactDOM.render(<App />, mountNode);

CommentApp.jsx

チュートリアルではCommentBox.jsxだったがAppで統一したかった為、名称変更。

/src/components/Comment/CommentApp.jsx
import React from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';
import $ from 'jquery';

export default class CommentApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [
        {
          author: 'Jack',
          text: 'just setting up my twttr'
        },
        {
          author: 'Evu',
          text: 'this tweet is nice'
        }
      ]
    };
    this.loadCommentsFromServer = this.loadCommentsFromServer.bind(this);
    this.handleCommentSubmit = this.handleCommentSubmit.bind(this);
  }

  componentDidMount() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  }

  loadCommentsFromServer() {
    // Ajaxは一旦コメントアウトでstateベースで実装

    // $.ajax({
    //   url: this.props.url,
    //   dataType: 'json',
    //   cache: false,
    //   success: (data) => { this.setState({ data }); },
    //   error: (xhr, status, err) => {
    //     console.log(this.props.url, status, err.toString());
    //   }
    // });

    this.setState({
      data: this.state.data
    });
  }

  handleCommentSubmit(comment) {
    this.setState({
      data: this.state.data.concat(comment)
    });
  }

  render() {
    return (
      <div className="commentApp">
        <h1>CommentApp</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
}

CommentApp.propTypes = {
  data: React.PropTypes.array,
  url: React.PropTypes.string,
  pollInterval: React.PropTypes.number
};

CommentList.jsx

/src/components/Comment/CommentList.jsx
import React from 'react';
import Comment from './Comment';

export default class CommentList extends React.Component {
  render() {
    let commentNodes = this.props.data.map((comment, i) => {
      return (
        <Comment key={i} author={comment.author}>{comment.text}</Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
}

CommentList.propTypes = {
  data: React.PropTypes.array
};

CommentForm.jsx

/src/components/Comment/CommentForm.jsx
import React from 'react';

export default class CommentForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(e) {
    // ブラウザ挙動停止
    e.preventDefault();

    // refの値を取得
    let author = this.refs.author.value;
    let text = this.refs.text.value;
    if (!text || !author) return;

    // サーバにデータを送信
    this.props.onCommentSubmit({
      author,
      text
    });

    // 値を空にする
    this.refs.author.value = '';
    this.refs.text.value = '';
  }

  render() {
    return (
      <form
        className="commentForm"
        onSubmit={this.handleSubmit}
      >
        <input
          type="text"
          placeholder="YourName"
          className="textArea"
          ref="author"
        />
        <input
          type="text"
          placeholder="Say Something..."
          ref="text"
          className="textArea"
        />
        <input
          type="submit"
          value="Tweet"
        />
      </form>
    );
  }
}

CommentForm.propTypes = {
  onCommentSubmit: React.PropTypes.func
};

Comment.jsx

/src/components/Comment/Comment.jsx
import marked from 'marked';
import React from 'react';

export default class Comment extends React.Component {

  render() {
    let rawMarkup = marked(this.props.children.toString(), { sanitize: true });
    return (
      <div className="comment">
        <h3 className="commentAuthor">
          {this.props.author}
        </h3>
        <span dangerouslySetInnerHTML={{ __html: rawMarkup }} />
      </div>
    );
  }
}

Comment.propTypes = {
  author: React.PropTypes.string,
  children: React.PropTypes.string
};

まとめ

やっぱりES6で書くと気持ちいい。
React関連の勉強をもっと進めよう。
gulpからWebpackに乗り換えたけど、設定さえ覚えればWebpackの方が便利かも。

Github

https://github.com/gemcook/kitchen.gemcook.com/tree/react-tutorial