React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用したものからAjax通信を組み込む

React は Viewしか担当していないので、その他の機能を含めたアプリのスケルトン作成を目指す。

Node.js、npm パッケージ管理、および、ブラウザが対応していないECMAScriptバージョンの利用にBabelを利用するなどを含めた環境構築、ページ遷移のためのReact Router、アプリケーションアーキテクチャとしてのRedux、非同期処理のためのRedux-Saga をとりあえず、最低限のレベルで組み合わせた。

  1. React 開発の全体像を把握しつつ開発環境を整える
  2. React の単純なサンプルに React Router を適用する
  3. React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用する

次は、Ajax通信部分を組み込む。

jQuery.ajaxの代わりにSuperAgentを使う

Ajax の部分だけを利用するのに、jQueryはオーバースペック(かつ容量を無駄に消費)してしまうため、HTTP通信の特化した、SuperAgetnt を使用することとする。

github : https://github.com/pppiroto/react_get_start.git

1.インストール

SuperAgent

npm install --save superagent

2.実装

2.1 ダミーAPIの結果

まずは、疎通をしたいだけなので、APIの結果を想定した、json ファイルを作成する。

/hello_successurl.json

{
    "message":"HELLO FROM API"
}

2.2 Action

以下のActionを追加する。

  • HELLO_API : 上記で作成したダミーjsonを取得する場合に呼び出す
  • HELLO_API_SUCCESS : 取得が成功した場合に呼び出す
  • HELLO_API_FAIL : 取得が失敗した場合に呼び出す

/actions/index.js

import { createAction } from 'redux-actions';

export const HELLO = 'HELLO';
export const HELLO_API = 'HELLO_API';
export const HELLO_API_SUCCESS = 'HELLO_API_SUCCESS';
export const HELLO_API_FAIL = 'HELLO_API_FAIL';

export const helloAction = createAction(HELLO);
export const helloApiAction = createAction(HELLO_API);
export const helloApiSuccessAction = createAction(HELLO_API_SUCCESS);
export const helloApiFailAction = createAction(HELLO_API_FAIL);

2.3 Ajax API

  • Ajax 通信を行い、データを取得する関数を作成、ここで、SuperAgentを使用する。
  • データの非同期取得のために、Promise を使用する(Promiseについては、こちらを参照)

以下は確認用のやっつけコード

  • id として、2.1 のダミーファイル名の一部を渡すことで、取得成功、失敗を確認
  • クエリパラメータとしてダミーのランダム文字列を付与することで、ブラウザがキャッシュするのを抑制

services/helloApi.js

import request from 'superagent';

// http://qiita.com/salesone/items/356572e689b9c2099c5c
export function helloApi(id) {
    console.log("helloApi(" + id + ")");
    return new Promise(
        (resolve, reject) => {
            request
                .get("./hello_{0}.json?dt=".replace("{0}", id) + Math.random().toString().replace(".", ""))
                .end(function (err, res) {
                    if (err) {
                        reject(err);
                    } else {
                        resolve({ payload: res.body });
                    }
                });
        }
    );
}

2.4 Sagas

上記で作成したAPIを呼び出し、成功/失敗に応じたActionを値をセットし呼び出す。
/sagas/index.js

import { takeEvery } from 'redux-saga';
import { call, put } from 'redux-saga/effects';
import {
    HELLO, helloAction,
    HELLO_API, helloApiAction,
    HELLO_API_SUCCESS, helloApiSuccessAction,
    HELLO_API_FAIL, helloApiFailAction
} from '../actions';
import { helloApi } from '../services/helloApi';

export function* getHelloApi(action) {
    console.log(helloApiSuccessAction());
    try {
        console.log(action);
        const data = yield call(helloApi, action.payload.id);
        yield put(helloApiSuccessAction(data.payload));
    } catch (error) {
        yield put(helloApiFailAction(error));
    }
}

// https://redux-saga.js.org/docs/basics/UsingSagaHelpers.html
export default function* rootSaga() {
    //yield* takeEvery(HELLO, helloAction);
    yield* takeEvery(HELLO_API, getHelloApi);
}

2.5 Reducer

Actionに設定された値から状態値を生成する。

ダミーの単純な処理のため、識別のため、末尾に処理時間を付与している。

/reducers/hello.js

import { HELLO, HELLO_API, HELLO_API_SUCCESS, HELLO_API_FAIL } from '../actions';

export default function hello(state="", action) {
    var suffix = " " + (new Date()).toLocaleTimeString("ja-JP");
    switch (action.type) {
        case HELLO:
            return action.payload + ",hello" + suffix;
        case HELLO_API_SUCCESS:
            return action.payload.message + suffix;
        case HELLO_API_FAIL:
            return action.payload.toString() + suffix;
        default:
            return state;
    }
}

2.6 App

  • Home コンポーネントに、成功/失敗のAPI呼び出しボタンを配置。
  • name 属性の値をActionに引き渡す

/containers/app.js

import React, { Component } from 'react';
import { BrowserRouter, Route, Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'
import { helloAction, helloApiAction } from '../actions';

class Home extends Component {
  handleMessage() {
    this.props.dispatch(helloAction('Yes'));
  }
  handleMessageApi(event) {
    this.props.dispatch(helloApiAction({id:event.target.name}));
  }
  render () {
    return (
      <div>
        <h2>Home</h2>
        { this.props.hello }
        <br />
        <button onClick={ this.handleMessage.bind(this) } >Hello</button>
        <br />
        <button name="successurl" onClick={ this.handleMessageApi.bind(this) } >API(Sucess)</button>
        <br />
        <button name="failurl" onClick={ this.handleMessageApi.bind(this) } >API(Fail)</button>
      </div>
    );
  }
}
 
const About = () => (
  <div><h2>About</h2></div>
)
const Topics = () => (
  <div><h2>Topics</h2></div>
)

class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <div>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/about">About</Link></li>
            <li><Link to="/topics">Topics</Link></li>
          </ul>
          <hr />
          {/* http://qiita.com/kuy/items/869aeb7b403ea7a8fd8a */}
          <Route exact path="/" component={connect(state => state)(Home)} />
          <Route exact path="/about" component={About} />
          <Route exact path="/topics" component={Topics} />
        </div>
      </BrowserRouter>
    );
  }
}

// http://qiita.com/MegaBlackLabel/items/df868e734d199071b883#_reference-863a1e1485bf47f046e5
function mapStateToProps(state) {
  return {
    message:state.hello
  };
}

// https://stackoverflow.com/questions/43350683/react-router-uncaught-typeerror-cannot-read-property-route-of-undefined
// export default withRouter(connect(mapStateToProps)(App))
export default connect(state => state)(App)

3.実行

3.1 成功

成功ボタンを押下、json取得し、Actionによりstateが更新され、そうていされた結果が表示されたOK。

super_agent01

3.2 失敗(存在しないURLを指定)

失敗ボタン押下で、存在しないURLへアクセスしに行き、結果エラーメッセージが画面表示されたOK。

super_agent03

ようやく最低ラインにたどり着いた。。。

  1. React 開発の全体像を把握しつつ開発環境を整える
  2. React の単純なサンプルに React Router を適用する
  3. React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用する
  4. React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用したものからAjax通信を組み込む
  5. React環境->React Router->Redux-Saga->SuperAgent に Bootstrapを適用する