Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

Last updated at Posted at 2015-10-15

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

  1. React Tutorial Example (Express)

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

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

v0.14 から ReactReactDOM の2つのパッケージに分割されました。
React Tutorial も ReactDOM のアップデートがされているので ReduxSocket.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

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

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

特に GitTutorial に必要ありません。


React

react

Tutorial

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

Style

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

Layout

  • ExpressLayoutTutorialSource からコピーしたい所ですが、テンプレートに Jade を利用しているのでHTMLを変換しました。
  • marked.min.jsTutorialAdding 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 に記述します。
  • しかし、 ReactVirtual DOM なのでレイアウトに ID (#content) 以外のコードを記述しません。

views/index.jade:

extends layout

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

Script

  • Tutorialtutorial1.js から tutorial20.jsexample.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 では CommentListComment が構造で追加されて、props で値が表示されています。

tutorial_4-5

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

Tutorial 6,7

tutorial_6-7

anotheranother に変換されていますね。

Tutorial 8,9,10

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

tutorial_8-10

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

Search Comments API

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

  • Express の便利な RouterComments を実装します。

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

送信するとデータベースもどきに authortext が保存されます。
これで ReactComments 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
  • ExpressWebpack の機能を追加します。

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.jsentry に設定します。

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.jsscript で参照するように編集します。

views/index.jade:

extends layout

block content
  #content
  script(src='static/bundle.js')
  • JQueryReactimport に記述します。

public/javascripts/example.js:

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

次の ReduxTutorialECMAScript 6 で記述しています。


Redux

redux logo

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

Yeoman

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

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

$ npm install --global yo
$ npm install --global generator-redux
$ yo redux
  • actions
    • Store にアプリケーションのデータを送信する情報が設置される。
  • components
    • Component(構成部品) が設置される。
  • constants
    • ActionReducerconst が設置される。
  • 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;
  • Webpackentryindex.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 が登場します。
他に ReactRedux をつなぐ 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>
    );
  }
});
  • Actiondispatch に登録する。

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;
  }
}
  • onCommentSubmitcreateComment を設定します。

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.findDOMNoderefs の 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

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


Tips

WebSocket

サーバーとの通信に Ajax でなく WebSocket を利用してみましょう。
WebSocketSocket.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 commentsrecieve commentscreate 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);
  };
}
  • componentWillMountrecieve commentsSubscription を設置する。

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 BoxSocket.IORooms 機能で少し手を入れるとチャットルームが作れますね。


PropTypes

PropTypesComponent の引数(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?