82
84

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React Tutorial Example

82
Last updated at Posted at 2015-10-15

項目が多いので React tutorial example series に分けました。
モジュールのバーッションをアップデートして、内容も少しアップグレードしています。

  1. React Tutorial Example (Express)

こちらはバージョンが古い記事です。

React は ユーザインターフェース を作成する JavaScript のライブラリです。
Virtual DOM や Data Flow が特徴で主に Facebook で開発されています。

v0.14 から React と ReactDOM の2つのパッケージに分割されました。
React Tutorial も ReactDOM のアップデートがされているので Redux や Socket.IO を利用してサンプルを実装しました。

実装した React Tutorial Example には6つの Tag でコードの差分を見ることができます。

Environment

環境は React v0.14 がリリースされた時点の最新のバージョンで開発しています。

  • node: v4.1.2
  • npm: v2.14.4
  • express: v4.13.1
  • react: v0.14.0
  • redux: v3.0.2
  • socket.io: v1.3.7

Express

Webサーバーには Express を利用しました。
Express Generator で簡単にWebサーバーが構築できます。

$ npm install express-generator -g
$ express react-tutorial-example
$ cd react-tutorial-example && npm install
$ DEBUG=react-tutorial-example:* npm start

express

ブラウザで http://localhost:3000/ にアクセスしましょう。
Welcome to Express が表示されたら OK です。
次に進みましょう。


Git

SCM は Tutorial の進みの差分を見直すことが出来て便利です。
今回は 20 程度の Commit で実装しました。

$ echo node_modules/ > .gitignore
$ git init
$ git add --all
$ git commit -m "Initial commit"

特に Git は Tutorial に必要ありません。


React

react

Tutorial

React で実際に Comments Box を作成しながら Virtual DOM や Data Flow を体験しましょう。
(こちらに Tutorial の Source があります。)

Style

  • まずは Express Generator で作成した Express の Stylesheet を Tutorial の Source からコピーします。
$ curl https://raw.githubusercontent.com/reactjs/react-tutorial/master/public/css/base.css -o ./public/stylesheets/style.css

Layout

  • Express の Layout も Tutorial の Source からコピーしたい所ですが、テンプレートに Jade を利用しているのでHTMLを変換しました。
  • marked.min.js は Tutorial の Adding Markdown で利用しますが、ここで追加しています。

views/layout.jade:

doctype html
html
  head
    meta(charset='utf-8')
    title React Tutorial
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js')
  body
    block content
  • Express ではトップページのレイアウトを index.jade に記述します。
  • しかし、 React は Virtual DOM なのでレイアウトに ID (#content) 以外のコードを記述しません。

views/index.jade:

extends layout

block content
  #content
  script(type='text/babel', src='/javascripts/example.js')
  script(type='text/babel').

Script

  • Tutorial の tutorial1.js から tutorial20.js を example.js に記述しながら進みます。

public/javascripts/example.js:

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});

ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);

それでは Tutorial を始めましょう。

Tutorial 1

tutorial_0-1

いつもの Hello, world! で始まります。

Tutorial 2,3

tutorial_2-3

ふたつの Hello, world! が表示されました。こうして Component ごとに分割してコードを記述します。

Tutorial 4,5

  • using-props では CommentList に Comment が構造で追加されて、props で値が表示されています。

tutorial_4-5

データは上位の Component から下位の Component に流れます。

Tutorial 6,7

tutorial_6-7

another が another に変換されていますね。

Tutorial 8,9,10

  • hook-up-the-data-model では CommentList にデータモデルから値が渡されて表示されています。

tutorial_8-10

表示は変わっていませんが、性能が大きく向上しました。

Search Comments API

次の Tutorial から サーバーに API が必要なので
Express に Comments を取得する API を実装します。

  • Express の便利な Router で Comments を実装します。

app.js:

var comments = require('./routes/api/comments');
app.use('/api/comments', comments);
  • ファイルを読み込んで、レスポンスを作成します。

routes/api/comments.js:

var fs = require('fs');
var express = require('express');
var router = express.Router();

/* GET comments listing. */
router.get('/', function(req, res, next) {
  fs.readFile('db/comments.json', function(err, data) {
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});

module.exports = router;
  • データベースもどきは JSON にします。

db/comments.json:

[
  {"author": "Pete Hunt", "text": "This is one comment"},
  {"author": "Jordan Walke", "text": "This is *another* comment"}
]

JavaScript Only は素敵!

Tutorial 11 - 14

tutorial_11-14

db/comments.json の値を変更すると数秒後に値の表示が変わります。

Create Comment API

今度は Express に Comment を登録する API を実装します。

  • 先ほどの Router に router.post を追加するだけで完成です。

routes/api/comments.js:

/* POST comment creating. */
router.post('/', function(req, res) {
  fs.readFile('db/comments.json', function(err, data) {
    var comments = JSON.parse(data);
    comments.push(req.body);
    fs.writeFile('db/comments.json', JSON.stringify(comments, null, 4), function(err) {
      res.setHeader('Cache-Control', 'no-cache');
      res.json(comments);
    });
  });
});

Tutorial 15 - 20

tutorial_15-20

送信するとデータベースもどきに author と text が保存されます。
これで React の Comments Box が完成しました。


Webpack

webpack logo

Webpack は依存関係のモジュールを取込み、静的な Assets を生成するライブラリです。
今回は先ほどの Tutorial で利用したモジュールを Webpack から生成しましょう。

$ npm install webpack --save-dev
$ npm install webpack-dev-middleware --save-dev
$ npm install babel-loader --save-dev
$ npm install jquery --save
$ npm install react --save
$ npm install react-dom --save
  • Express に Webpack の機能を追加します。

app.js:

var webpack = require('webpack');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpackConfig = require('./config/webpack.config');

// webpack setup
var compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler, {
  noInfo: true, publicPath: webpackConfig.output.publicPath
}));
  • Tutorial で記述した example.js を entry に設定します。

config/webpack.config.js:

module.exports = {
  entry: [
    './public/javascripts/example.js'
  ],
  output: {
    path: __dirname + '/static/',
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loaders: ['babel'],
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: ['', '.js']
  }
};
  • marked 以外は Webpack で生成するので、layout.jade から除外します。

views/layout.jade:

doctype html
html
  head
    meta(charset='utf-8')
    title React Tutorial
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js')
  body
    block content
  • Webpack が生成する bundle.js を script で参照するように編集します。

views/index.jade:

extends layout

block content
  #content
  script(src='static/bundle.js')
  • JQuery や React を import に記述します。

public/javascripts/example.js:

import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';

次の Redux の Tutorial は ECMAScript 6 で記述しています。


Redux

redux logo

Redux は Flux の Framework で主に Action, Reducer, Store で構成されます。

Yeoman

Redux の examples を参照するとディレクトリの構造にルールがあるのが解ります。

この Redux Tutorial は Yeoman の Redux Generator で出力された構造を参考にしています。
最初から React Tutorial を Redux で順に進めて行くので yo redux コマンドの実行は不要です!

$ npm install --global yo
$ npm install --global generator-redux
$ yo redux
  • actions
    • Store にアプリケーションのデータを送信する情報が設置される。
  • components
    • Component(構成部品) が設置される。
  • constants
    • Action と Reducer の const が設置される。
  • containers
    • App.js にアプリケーションの起点が設置される。
  • reducers
    • Action に応じてアプリケーションの状態を変更する機構が設置される。
  • store
    • アプリケーションの状態を保持する機構と状態を更新する機構が設置される。
  • utils
    • DevTools などが設置される。

Tutorial

Tutorial 1

  • app ディレクトリに各役割のディレクトリを設置しました。

app/index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';

ReactDOM.render(
  <App />,
  document.getElementById('content')
);
  • アプリケーションの起点の App.js を設置します。

app/containers/App.js:

import React from 'react';
import Home from '../components/Home';

export default React.createClass({
  render() {
    return (
      <div>
        <Home />
      </div>
    );
  }
});
  • コンポーネントの起点の Home.js を設置します。

app/components/Home.js:

import React, {Component} from 'react';
import CommentBox from './CommentBox';

class Home extends Component {
  render() {
    return (
      <div>
        <CommentBox />
      </div>
    );
  }
}

export default Home
  • コンポーネントに CommentBox.js を設置します。

app/components/CommentBox.js:

import React, {Component} from 'react';

class CommentBox extends Component {
  render() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
}

export default CommentBox;
  • Webpack の entry を index.js に変更します。

config/webpack.config.js:

module.exports = {
  entry: [
    './app/index.js'
  ],

tutorial_0-1

再び Hello, world! が来ました。
まだ、Redux は利用していません。

Tutorial 2,3

  • コンポーネントはそれぞれのファイルに設置します。

app/components/CommentBox.js:

import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';

class CommentBox extends Component {
  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
}

export default CommentBox;
  • コンポーネントのファイルが Fat になるのを抑止されます。

app/components/CommentList.js:

import React, {Component} from 'react';

class CommentList extends Component {
  render() {
    return (
      <div className="commentList">
        Hello, world! I am a CommentList.
      </div>
    );
  }
}

export default CommentList;
  • モジュールが個々にできるので、テストも小さくできます。

app/components/CommentForm.js:

import React, {Component} from 'react';

class CommentForm extends Component {
  render() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
}

export default CommentForm;

tutorial_2-3

ふたつの Hello, world! が表示されていますね。
しかしまだ、Redux は利用していません。

Tutorial 4,5

  • 上位の CommentList から Comment にデータを流します。

app/components/CommentList.js:

import React, {Component} from 'react';
import Comment from './Comment';

class CommentList extends Component {
  render() {
    return (
      <div className="commentList">
        <Comment author="Pete Hunt">This is one comment</Comment>
        <Comment author="Jordan Walke">This is *another* comment</Comment>
      </div>
    );
  }
}

export default CommentList;
  • 下位の props でデータを表示しています。

app/components/Comment.js:

import React, {Component} from 'react';

class Comment extends Component {
  render() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {this.props.children}
      </div>
    );
  }
}

export default Comment;

tutorial_4-5

ここでもまだ、Redux は利用していません。

Tutorial 6,7

  • ECMAScript 6 なので function の記述が不要です。

app/components/Comment.js:

import React, {Component} from 'react';

class Comment extends Component {
  rawMarkup() {
    let rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return { __html: rawMarkup };
  }

  render() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>
    );
  }
}

export default Comment;

tutorial_6-7

もちろん Redux は利用していません。

Tutorial 8,9,10

  • データを Home から下位に流します。

app/components/Home.js:

import React, {Component} from 'react';
import CommentBox from './CommentBox';

const data = [
  {author: "Pete Hunt", text: "This is one comment"},
  {author: "Jordan Walke", text: "This is *another* comment"}
];

class Home extends Component {
  render() {
    return (
      <div>
        <CommentBox data={data} />
      </div>
    );
  }
}

export default Home
  • データを CommentBox から下位に流します。

app/components/CommentBox.js:

import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';

class CommentBox extends Component {
  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
}

export default CommentBox;
  • データを CommentList から下位に流します。

app/components/CommentList.js:

import React, {Component} from 'react';
import Comment from './Comment';

class CommentList extends Component {
  render() {
    const commentNodes = this.props.data.map((comment, index) => {
      return (
        <Comment author={comment.author} key={index}>
          {comment.text}
        </Comment>
      );
    });

    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
}

export default CommentList;

tutorial_8-10

Redux を利用しなくともディレクトリの構造やモジュール化のテクニックで
ファイルを小さく保つことができます。

Tutorial 11 - 14

GET /api/comments から値が取得するアクションが発生するので、ここで Redux が登場します。
他に React と Redux をつなぐ React Redux や 非同期を調整する Redux Thunk のライブラリを利用します。

$ npm install redux --save
$ npm install react-redux --save
$ npm install redux-thunk --save
  • まず、アクションを宣言します。

app/constants/ActionTypes.js:

export const RECEIVE_COMMENTS = 'RECEIVE_COMMENTS';
  • Comment の Search と Recieve のアクションを追加します。
  • 非同期は ECMAScript 6 から利用できる Promise で宣言しています。

app/actions/CommentActions.js:

import * as ActionTypes from '../constants/ActionTypes';
import $ from 'jquery';

export function recieveComments(comments) {
  return {
    type: ActionTypes.RECEIVE_COMMENTS,
    comments
  };
}

export function searchComments() {
  return dispatch => {
    let promise = new Promise((resolve, reject) => {
      $.ajax({
        url: '/api/comments',
        dataType: 'json',
        cache: false,
        success(data) {
          resolve(data);
        },
        error(xhr, status, err) {
          reject(err);
        }
      });
    });

    promise.then((data) => {
      dispatch(recieveComments(data));
    }).catch((err) => {
      console.error(err);
    });
  };
}
  • 複数の Reducer は index.js で宣言します。

app/reducers/index.js:

export {default as Comment} from './Comment';
  • Comment の Recieve アクションから状態を取得します。

app/reducers/Comment.js:

import * as ActionTypes from '../constants/ActionTypes';

const initialState = {comments: []};

export default function(state = initialState, action) {
  switch (action.type) {
  case ActionTypes.RECEIVE_COMMENTS:
    return {comments: action.comments};
  default:
    return state;
  }
}
  • Store を生成するときに Middleware や Reducer を取り入れます。

app/store/configureStore.js:

import {createStore, applyMiddleware, combineReducers} from 'redux';
import thunkMiddleware from 'redux-thunk';
import * as reducers from '../reducers/index';

let createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
const rootReducer = combineReducers(reducers);

export default function configureStore(initialState) {
  return createStoreWithMiddleware(rootReducer, initialState);
}
  • React Redux で Provider を宣言をして Store を流します。

app/containers/App.js:

import React from 'react';
import {Provider} from 'react-redux';
import configureStore from '../store/configureStore';
import Home from '../components/Home';

const store = configureStore();

export default React.createClass({
  render() {
    return (
      <div>
        <Provider store={store}>
          <Home />
        </Provider>
      </div>
    );
  }
});
  • Action を dispatch に登録する。

app/components/Home.js:

import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as CommentActions from '../actions/CommentActions';
import CommentBox from './CommentBox';

class Home extends Component {
  render() {
    const {dispatch, comments} = this.props;
    const actions = bindActionCreators(CommentActions, dispatch);
    return (
      <div>
        <CommentBox actions={actions} data={comments} />
      </div>
    );
  }
}

export default connect(state => state.Comment)(Home)
  • Search Comments のアクションを実施します。

app/components/CommentBox.js:

import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';

class CommentBox extends Component {
  componentDidMount() {
    this.props.actions.searchComments();
    setInterval(this.props.actions.searchComments, 2000);
  }

  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
}

export default CommentBox;

tutorial_11-14

構造的に小さなモジュールを繋げることは強いシステムになるので、Redux の考えは好ましいです。

Tutorial 15 - 20

  • コメントを追加するアクションを宣言します。

app/constants/ActionTypes.js:

export const ADD_COMMENT = 'ADD_COMMENT';
  • Comment の Create と Add のアクションを追加します。

app/actions/CommentActions.js:

export function addComment(comment) {
  return {
    type: ActionTypes.ADD_COMMENT,
    comment
  };
}

export function createComment(comment) {
  return dispatch => {
    dispatch(addComment(comment));
    let promise = new Promise((resolve, reject) => {
      $.ajax({
        url: '/api/comments',
        dataType: 'json',
        type: 'POST',
        data: comment,
        success(data) {
          resolve(data);
        },
        error(xhr, status, err) {
          reject(err);
        }
      });
    });

    promise.then((data) => {
      dispatch(recieveComments(data));
    }).catch((err) => {
      console.error(err);
    });
  };
}
  • ADD_COMMENT でコメントが追加されます。

app/reducers/Comment.js:

import * as ActionTypes from '../constants/ActionTypes';

const initialState = {comments: []};

export default function(state = initialState, action) {
  switch (action.type) {
  case ActionTypes.RECEIVE_COMMENTS:
    return {comments: action.comments};
  case ActionTypes.ADD_COMMENT:
    return {comments: state.comments.concat([action.comment])};
  default:
    return state;
  }
}
  • onCommentSubmit に createComment を設定します。

app/components/CommentBox.js:

import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';

class CommentBox extends Component {
  componentDidMount() {
    this.props.actions.searchComments();
    setInterval(this.props.actions.searchComments, 2000);
  }

  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm onCommentSubmit={this.props.actions.createComment} />
      </div>
    );
  }
}

export default CommentBox;
  • ReactDOM.findDOMNode で refs の DOM を操作します。

app/components/CommentForm.js:

import React, {Component} from 'react';
import ReactDOM from 'react-dom';

class CommentForm extends Component {
  handleSubmit(e) {
    e.preventDefault();
    const author = ReactDOM.findDOMNode(this.refs.author).value.trim();
    const text = ReactDOM.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) {
      return;
    }
    this.props.onCommentSubmit({author: author, text: text});
    ReactDOM.findDOMNode(this.refs.author).value = '';
    ReactDOM.findDOMNode(this.refs.text).value = '';
    return;
  }

  render() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
}

export default CommentForm;

tutorial_15-20

これで Redux の Comments Box が完成しました。
Redux も Tutorial が必要かなと思って、React Tutorial を題材にしましたが、Redux はもっと多機能なので学習を深めたい。


Tips

WebSocket

サーバーとの通信に Ajax でなく WebSocket を利用してみましょう。
WebSocket は Socket.IO のライブラリを利用します。

$ npm install socket.io --save
$ npm install socket.io-client --save

Server

  • Express の 実行ファイルの最後に socket.io の設定を追加します。

bin/www:

var io = require('socket.io')(server);
require('../channels/channel.js')(io);
  • サーバーに search comments と recieve comments と create comment のチャンネルを追加します。
  • また、setInterval をクライアントからサーバーに移設します。

channels/channel.js:

var fs = require('fs');

var channel = function(io) {
  io.on('connection', function(socket) {
    socket.on('search comments', function(){
      fs.readFile('db/comments.json', function(err, data) {
        socket.emit('recieve comments', JSON.parse(data));
      });
    });

    socket.on('create comment', function(comment){
      fs.readFile('db/comments.json', function(err, data) {
        var comments = JSON.parse(data);
        comments.push(comment);
        fs.writeFile('db/comments.json', JSON.stringify(comments, null, 4), function(err) {
          socket.emit('recieve comments', comments);
        });
      });
    });

    setInterval(function(){
      fs.readFile('db/comments.json', function(err, data) {
        socket.emit('recieve comments', JSON.parse(data));
      });
    }, 2000);
  })
};

module.exports = channel;

Client

  • $.ajax(jQuery) を socket.emit に置換えます。

app/actions/CommentActions.js:

import * as ActionTypes from '../constants/ActionTypes';
import io from 'socket.io-client';
export const socket = io('http://localhost:3000');

export function recieveComments(comments) {
  return {
    type: ActionTypes.RECEIVE_COMMENTS,
    comments
  };
}

export function addComment(comment) {
  return {
    type: ActionTypes.ADD_COMMENT,
    comment
  };
}

export function searchComments() {
  return dispatch => {
    socket.emit('search comments');
  };
}

export function createComment(comment) {
  return dispatch => {
    socket.emit('create comment', comment);
  };
}
  • componentWillMount に recieve comments の Subscription を設置する。

app/components/CommentBox.js:

import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';
import io from 'socket.io-client';
export const socket = io('http://localhost:3000');

class CommentBox extends Component {
  componentWillMount() {
    const {actions} = this.props;
    socket.on('recieve comments', function(comments) {
      actions.recieveComments(comments);
    });
  }

  componentDidMount() {
    this.props.actions.searchComments();
  }

  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm onCommentSubmit={this.props.actions.createComment} />
      </div>
    );
  }
}

export default CommentBox;

サーバーに socket.broadcast.emit を追加すると setInterval は不要です。
Comment Box は Socket.IO の Rooms 機能で少し手を入れるとチャットルームが作れますね。


PropTypes

PropTypesは Component の引数(props) を定義することができます。
可読性が向上するので利用しましょう。(型も豊富にあります。)

  • PropTypes.array
  • PropTypes.bool
  • PropTypes.func
  • PropTypes.number
  • PropTypes.object
  • PropTypes.string
  • PropTypes.node
  • PropTypes.element
  • PropTypes.instanceOf(Message)
  • PropTypes.oneOf(['News', 'Photos'])
  • PropTypes.oneOfType([PropTypes.string, PropTypes.number)
  • PropTypes.arrayOf(PropTypes.number)
  • PropTypes.objectOf(PropTypes.number)
  • PropTypes.shape({color: PropTypes.string, fontSize: PropTypes.number})
  • PropTypes.any.isRequired

Example

  • {Component, PropTypes} で宣言をして、propTypes = {} で定義します。

app/components/Comment.js:

import React, {Component, PropTypes} from 'react';

class Comment extends Component {
  rawMarkup() {
    let rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return { __html: rawMarkup };
  }

  render() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>
    );
  }
}

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

export default Comment;

Enjoy React

82
84
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
82
84

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?