React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用したものからAjax通信を組み込む
React は Viewしか担当していないので、その他の機能を含めたアプリのスケルトン作成を目指す。
Node.js、npm パッケージ管理、および、ブラウザが対応していないECMAScriptバージョンの利用にBabelを利用するなどを含めた環境構築、ページ遷移のためのReact Router、アプリケーションアーキテクチャとしてのRedux、非同期処理のためのRedux-Saga をとりあえず、最低限のレベルで組み合わせた。
- React 開発の全体像を把握しつつ開発環境を整える
- React の単純なサンプルに React Router を適用する
- React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用する
次は、Ajax通信部分を組み込む。
Ajax の部分だけを利用するのに、jQueryはオーバースペック(かつ容量を無駄に消費)してしまうため、HTTP通信の特化した、SuperAgetnt を使用することとする。
github : https://github.com/pppiroto/react_get_start.git
1.インストール
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。
3.2 失敗(存在しないURLを指定)
失敗ボタン押下で、存在しないURLへアクセスしに行き、結果エラーメッセージが画面表示されたOK。
ようやく最低ラインにたどり着いた。。。
- React 開発の全体像を把握しつつ開発環境を整える
- React の単純なサンプルに React Router を適用する
- React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用する
- React の単純なサンプルに React Router を組み込んだものに Redux-Saga を適用したものからAjax通信を組み込む
- React環境->React Router->Redux-Saga->SuperAgent に Bootstrapを適用する