ReactioでReactを導入した話

やっはろー！Gaiaxの@miniturboです！
この記事は[Gaiax Engineers' Advent Calendar 2015](http://qiita.com/advent-calendar/2015/gaiax)の19日目の記事として書いています。

最近では、システム障害対応に特化したサービス[Reactio](https://reactio.jp/)の開発を行っております。今回は、そのReactioに[React](https://facebook.github.io/react/)を導入したお話となります。

## React導入の背景

Reactioは、システム障害が発生した際、チームで迅速に対応・解決できるよう、タイムラインでのやり取りや障害情報の記録をリアルタイムに行えるようなWebアプリケーションになっています。
このリアルタイムアプリケーションの部分は、もともと社内での課題を解決するための社内ツールとして2013年に生まれたこともあり、当時流行していた[Backbone.js](http://backbonejs.org/)とWebSocketを使って実現しています。

素のBackbone.jsということもあり、当初はシンプルで見通しも良かったのですが、機能が増えるにつれてロジックは複雑になり、様々なイベントが飛び交い、どこで何が起こっているのか、データがどう流れているのかを追うのが困難になってしまいました。

そこで、何かしらの仕組み・枠組みを導入し、機能が複雑化してもデータやイベントの流れが把握しやすくなる必要が出てきました。
これがReact/Fluxを導入しようと思い立った背景です。

![Fluxのデータフロー](https://github.com/facebook/flux/raw/master/docs/img/flux-diagram-white-background.png)

※図は[Flux公式リポジトリ](https://github.com/facebook/flux)より引用

## 一部分を置き換える

React/Fluxを導入しよう！と、思い立ったはいいものの、既存のコードをすべてReactに置き換えていくのはとても大変です。影響範囲も広く、実装工数もとても大きくなってしまいます。
そこで、各機能のうち一番小さい機能を対象に、その部分だけReactに置き換えていく戦略をとり、Reactを小さく導入していくことにしました。

Reactioでは、システム障害1つをインシデントとして扱っています。
今回は、そのインシデントの名前の表示部分をReactioに置き換えています。

![image01.png](https://qiita-image-store.s3.amazonaws.com/0/6062/b602df85-573b-b29c-4c24-351ce1651027.png)

## 置き換えの準備

まずは、Reactを書き始められるよう、必要なモジュールを入れるところから始めました。
また、今回はFlux実装として[Redux](http://redux.js.org/)を利用しているので、そちらも合わせて入れました

```
$ npm install --save react react-dom redux react-redux
```

ディレクトリ構成は下記のような感じです。

```
./js
├── actions
│   └── index.js
├── components
│   └── IncidentName.js
├── index.js
├── reducers
│   └── index.js
├── store
└── utils
```

## 固定の文字列を表示してみる
まずはじめに、インシデント名の部分に固定の文字列をReactで表示してみることにしました。

通常ではアプリケーション全体をReactで`render`することが多いと思いますが、今回はインシデント名の部分のみReactで表示するため、HTML上のインシデント名の部分に空の`<div>`を設置します。

```
<html>
  ...
  <body>
    ...
    <div id="incident-name"></div>
    ...
  </body>
</html>
```

次に、インシデント名のコンポーネント`IncidentName`を用意します。

```components/IncidentName.js
import React, { Component } from 'react';

export default class IncidentName extends Component {
  render() {
    return (
      <div>インシデント名をReactで表示したい！</div>
    );
  }
}
```

そして、起点となる`index.js`で先ほどの`IncidentName`コンポーネントを`render`してみます。

```index.js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import IncidentName from './components/IncidentName';

render(
  (
    <Provider store={configureStore()}>
      <IncidentName />
    </Provider>
  ),
  document.getElementById('incident-name')
);
```

これで、ブラウザを読み込み直してみます。

![image02.png](https://qiita-image-store.s3.amazonaws.com/0/6062/5cc942e8-72fd-34c3-26d1-e130deaa0437.png)

無事に表示されました！

## API経由でインシデント名を表示する

次に、APIサーバから本来のインシデント名を取得して、表示してみることにしました。
`IncidentName`をマウントする際に、インシデント名を取得する`fetchIncidentName`というActionを実行してみることにします。

```components/IncidentName.js
import React, { Component } from 'react';
import { fetchIncidentName } from '../actions'

export default class IncidentName extends Component {
  componentDidMount() {
    this.props.dispatch(fetchIncidentName());
  }

  render() {
    return (
      <div>インシデント名をReactで表示したい！</div>
    );
  }
}
```

`fetchIncident`はこのように実装してみました。

```actions/index.js
export const fetchIncidentName = () => (dispatch) => {
  api.get('インシデント名取得APIのエンドポイント').then((json) => {
  	dispatch({ payload: json });
  });
}
```

取得したインシデント名を、Reducerで処理してStoreに格納します。

```reducers/index.js
import { combineReducers } from 'redux'

function incident(state = {}, action) {
  return { ...state, name: action.payload };
}

export default combineReducers({ incident });
```

最後に、IncidentNameとReduxをつなぎこんで、Storeの値をprops経由で表示できるようにします。


```components/IncidentName.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { fetchIncidentName } from '../actions'

@connect((state) => { incident: state.inident })
export default class IncidentName extends Component {
  componentDidMount() {
    this.props.dispatch(fetchIncidentName());
  }
  
  render() {
    return (
      <div>{this.props.incident.name}</div>
    );
  }
}

export default connect((state) => ({ incident: state.incident }))(App)
```

これで、ブラウザを読み込み直してみます。

![image03.png](https://qiita-image-store.s3.amazonaws.com/0/6062/1be05c86-6f0d-045c-622b-f86391a14b56.png)

本来のインシデント名が表示されましたね！


## 表示をリアルタイムに更新されるようにする

Reactioでは、インシデント名が変更された際に、WebSocketを使って変更内容がリアルタイムに伝わるようになっています。既存の実装では、WebSocketクライアントを`Backbone.Events`でラップしたもので実装しているのですが、今回は置き換えはせずそのままReactから利用することにしました。

インシデント名の取得と同じ要領で、WebSocketからインシデント名の変更を受け付けた際に`receiveIncidentName`というActionを実行してみることにします。

```components/IncidentName.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { fetchIncidentName, receiveIncidentName } from '../actions';

@connect((state) => { incident: state.inident })
export default class IncidentName extends Component {
  componentDidMount() {
    this.props.dispatch(fetchIncidentName());

    Stream.on('updated-incident-name', (name) => {
      this.props.dispatch(receiveIncidentName(name));
    });
  }
  
  render() {
    return (
      <div>{this.props.incident.name}</div>
    );
  }
}

export default connect((state) => ({ incident: state.incident }))(App)
```

`fetchIncident`と同様に`receiveIncidentName`を実装します。


```actions/index.js
export const fetchIncidentName = () => (dispatch) => {
  api.get('インシデント名取得APIのエンドポイント').then((json) => {
    dispatch({ payload: json });
  });
}

export const receiveIncidentName = (name) => {
  dispatch({ payload: name });
}

```

これでインシデント名がリアルタイムに更新されるようになりました！

## まとめ

実際にはもう少し工夫していますが、概ねこの流れでReactioでReactを（ちょこっとだけ）導入することができました！これで、導入の背景にあった課題も解決し、「ReactioはやっぱりReact使っているんですか？」という質問にも安心して答えられそうです :wink: 

現在は、この置き換え部分を足がかりに、新規／既存のコードを少しずつReact/Fluxに置き換えていっています。一度に置き換えようとせず、一つずつ改善を繰り返していくのがいいのだなあ、と改めて思った次第です。どこかで参考になれば幸いです。

明日は北海道が生んだ弊社インフラチームのエース、 @akms くんです！お楽しみに！