6
5

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.

GaiaxAdvent Calendar 2015

Day 19

ReactioでReactを導入した話

Last updated at Posted at 2015-12-18

やっはろー!Gaiaxの@miniturboです!
この記事はGaiax Engineers' Advent Calendar 2015の19日目の記事として書いています。

最近では、システム障害対応に特化したサービスReactioの開発を行っております。今回は、そのReactioにReactを導入したお話となります。

React導入の背景

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

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

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

Fluxのデータフロー

※図はFlux公式リポジトリより引用

一部分を置き換える

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

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

image01.png

置き換えの準備

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

$ 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

無事に表示されました!

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

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

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

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 くんです!お楽しみに!

6
5
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
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?