React(ES2015)とMilkcocoaでリアルタイムチャットを作ってみる #reactnative_meetup #mlkcca


昨日の#jxugではAndroid(けっきょくC#じゃなくてJavaで)とMilkcocoaをつなげてみてました。 

> [AndroidからMilkcocoaを使って見るメモ #mlkcca](http://qiita.com/n0bisuke/items/2680938a3ce9fd70ce88)


[React入門者向けハンズオン](http://connpass.com/event/36786/)で作ってみました。ハンズオンで使った[Create React App](http://jp.techcrunch.com/2016/07/23/20160722facebooks-new-open-source-project-makes-it-easier-to-get-started-with-react/)も参考にしてます。

es2015な感じで書いたので誰かレビューしてほしい...

ちなみに1年前にこんな記事も書いてたらしいですがすっかり忘れてました。あのときはFluxxorが流行ってた記憶があります。

> [React+Fluxxor+MilkcocoaでfluxなTODOアプリ #geekhouseday
](http://qiita.com/n0bisuke/items/f28016de82136a2a4d26)

## milkcocoa

IoTのバックエンドサービスとして使われているリアルタイム通信サポートのバックエンドサービス https://mlkcca.com/

今回はチャットの裏側に使います。

## 公式チュートリアルをいじってReactでチャットを作ってみた

### まずは完成品

URL: https://private-chipmunk-54046.netlify.com/

(いつまで稼働させてるかはわからないです)

![](https://i.gyazo.com/fcb7b91cf20f12315b93a20103cb3769.gif)

リアルタイムなチャットができます。

### 参考にしたチュートリアル

* URL: https://facebook.github.io/react/docs/tutorial-ja-JP.html

このチュートリアルをやるとマークダウン対応のコメント欄みたいなものをつくれます。

メインのソースコードはGitHub上の`example.js`です。

> https://github.com/reactjs/react-tutorial/blob/master/public/scripts/example.js


### 書いたソースコード

先ほどの`example.js`をもとに書き換えてます。

```app.js
const milkcocoa = new MilkCocoa('your-app-id.mlkcca.com');
const ds = milkcocoa.dataStore('react-chat');

//CommentBox -> CommentList -> Comment
class Comment extends React.Component {
    constructor(props) {
        super(props);
    };

    rawMarkup(){
        let md = new Remarkable();
        let rawMarkup = md.render(this.props.children.toString());
        return { __html: rawMarkup };
    };

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

//CommentBox->CommentList
class CommentList extends React.Component {
    render() {
        let commentNodes = this.props.data.map( (item) => {
            let comment = item.value;
            return (
                <Comment author={comment.author} key={item.id}>
                    {comment.text}
                </Comment>
            );
        });

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

//CommentBox->CommentForm
class CommentForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {author: '', text: ''};
    };
    
    handleAuthorChange (e) {
        this.setState({author: e.target.value});
    };
    
    handleTextChange (e) {
        this.setState({text: e.target.value});
    };
    
    handleSubmit (e) {
        e.preventDefault();
        let author = this.state.author.trim();
        let text = this.state.text.trim();
        if (!text || !author) return;
        this.props.onCommentSubmit({author: author, text: text});
        // this.setState({author: '', text: ''});
        this.setState({text: ''}); //名前は残す
    };

    render() {
        return (
            <form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
                <input
                    type="text"
                    placeholder="名前を入力してね。"
                    value={this.state.author}
                    onChange={this.handleAuthorChange.bind(this)}
                />
                <input
                    type="text"
                    placeholder="マークダウン使えるよ"
                    value={this.state.text}
                    onChange={this.handleTextChange.bind(this)}
                />
                <input type="submit" value="Post" />
            </form>
        );
    };
};

//CommentBox
class CommentBox extends React.Component {
    constructor(props) {
        super(props);
        this.state = {data: []};
    };
    
    componentDidMount () {
        this.loadCommentsFromServer();
        ds.on('push', this.loadCommentsFromServer.bind(this));
    };

    loadCommentsFromServer () {
        let history = ds.history();
        history.on('data', (data) => {
            this.setState({data: data});
        });
        history.run();
    };

    handleCommentSubmit (comment) {
        ds.push(comment);
    };
    
    render() {
        return (
            <div className="commentBox">
                <CommentForm onCommentSubmit={this.handleCommentSubmit} />
                <CommentList data={this.state.data} />
            </div>
        );
    };
};

class App extends React.Component {
    render(){
        return(
            <div className="App">
                <div className="App-header">
                    <img src="../logo.svg" className="App-logo" alt="logo" />
                    <h1>Milkcocoa+Reactでリアルタイムチャット</h1>
                </div>
                <CommentBox />
            </div>
        );
    }
}

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

コメントないけど自分用メモなつもりなのであしからず。



### ES5 -> ES2015書き換え

[参考にしたチュートリアル](https://facebook.github.io/react/docs/tutorial.html)はES5ベースの書き方になっているのでES2015な書き方に変更します。

* [React v0.13.0 Beta1でclassでComponentが作れるようになった](http://blog.koba04.com/post/2015/01/28/published-react-v0.13.0-beta1/)
* [ES5のReact.jsソースをES6ベースに書き換える](http://qiita.com/kuniken/items/2e850daa26a10b5098d6)

これらを参考にして`React.createClass()`を`React.Component`に書き換えました。

主にこの辺りで若干手こずりました。

* getInitialState()が使えないのでconstructorで初期化

```
getInitialState: function() {
    return {author: '', text: ''};
  },
```

↓ stateの初期化の仕方が変わる

```
    constructor(props) {
        super(props);
        this.state = {author: '', text: ''};
    };
    
```

* thisのオートバインディングがなくなるので自分でバインドする


```
<form className="commentForm" onSubmit={this.handleSubmit}>
```

↓ ここもバインドしてあげる

```
<form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
```

みたいな書き方に変更します。

### Milkcocoa的な部分

CommentBoxのコンポーネント内のメソッドで処理してます。

* handleCommentSubmit内でデータ保存 / pushメソッド

MilkcocoaのpushメソッドをhandleCommentSubmit()で行ってます。

```
    handleCommentSubmit (comment) {
        ds.push(comment);
    };
```

pushメソッドが実行されると接続してるクライアントにpushイベントが発火されます。

* loadCommentsFromsSever内でデータ取得 / historyメソッド

```
    loadCommentsFromServer () {
        let history = ds.history();
        history.on('data', (data) => {
            this.setState({data: data});
        });
        history.run();
    };
```

* historyメソッドの初期呼び出しとpushイベントの監視 / onメソッド

[今日のハンズオン資料](https://github.com/besutome/slides/tree/master/20160918-react-handson#component-life-cycle)にあるようにcomponentDidMountはコンポーネントがレンダリングされた後に一度だけ呼ばれます。

なのでそのタイミングでhistoryを発火させる(初期ロード)のと、onメソッドでpushイベントを監視を開始します。

これで他の人がコメントするたびにhistoryが実行されてデータが更新されてリアルタイムチャットになります。

```
componentDidMount () {
        this.loadCommentsFromServer();
        ds.on('push', this.loadCommentsFromServer.bind(this));
    };
```

## 雑感

ハンズオン中につくったので雑かもしれないですが取りあえずまとめてみました。

久しぶりにReact触ったけど、バージョンアップによって書き方が変わっててどれを参考にするのがいいのかってちゃんと動きを追ってないと大変そうだなぁという印象でした苦笑

でもとりあえずリハビリな感じになって楽しかった！