あらすじ
前回は redux による state の管理について触れました。 今回は http 通信などによる非同期処理の方法について触れます。
なお外部との通信に https://jsonplaceholder.typicode.com を利用しています。
目次
- 開発環境を準備
- React の基本的な Life Cycle に触れる
- redux に触れる
- redux-saga に触れる <= 今日やること
- react router に触れる
- npm で公開されている components を導入して echo system を体感する
- redux-form に触れる
- react-select に触れる
準備
npm install --save redux-saga axios npm install --save-dev http-proxy-middleware touch src/api.js src/sagas.js
create src/api.js
import axios from 'axios'
const BASE_URL = window.location.origin
export function getPosts({userId}) {
return axios.get(`${BASE_URL}/api/posts?userId=${userId}`).then((res) => {
return { "posts": res.data }
})
}
export default { getPosts }
edit src/actions.js
import { createAction } from "redux-actions"
import api from './api'
export const FETCH_REQUESTED = "FETCH_REQUESTED"
export const FETCH_SUCCESSED = "FETCH_SUCCESSED"
export const FETCH_FAILED = "FETCH_FAILED"
export const fetchRequested = createAction(FETCH_REQUESTED)
export const fetchSuccessed = createAction(FETCH_SUCCESSED)
export const fetchFailed = createAction(FETCH_FAILED)
export function getPosts({userId}) {
return fetchRequested(() => {
return api.getPosts({userId})
})
}
edit src/reducers/index.js
import { combineReducers } from 'redux'
import { FETCH_SUCCESSED } from '../actions'
const initialState = {
posts: []
}
function fetch(state = initialState, action) {
switch (action.type) {
case FETCH_SUCCESSED:
return Object.assign({}, state, action.payload)
default:
return state
}
}
const rootReducer = combineReducers({
fetch,
})
export default rootReducer
edit src/store/configureStore.js
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import createLogger from 'redux-logger'
import rootReducers from '../reducers'
import rootSaga from "../sagas"
const sagaMiddleware = createSagaMiddleware()
const logger = createLogger()
const middlewares = [sagaMiddleware, logger]
const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore)
export default function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducers, initialState)
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default
store.replaceReducer(nextRootReducer)
})
}
sagaMiddleware.run(rootSaga)
return store
}
create src/sagas
import { takeEvery } from "redux-saga"
import { put, call } from "redux-saga/effects"
import api from "./api"
import {
FETCH_REQUESTED,
fetchSuccessed,
fetchFailed,
} from "./actions"
function* fetch(action) {
try {
const data = yield call(action.payload)
yield put(fetchSuccessed(data))
} catch (err) {
yield put(fetchFailed(err))
}
}
export default function* rootSaga() {
yield takeEvery(FETCH_REQUESTED, fetch)
}
edit src/containers/Posts.js
import React, {Component} from 'react'
import { connect } from 'react-redux'
import { fetchRequest } from '../actions'
import { PostItem } from '../components/Posts'
import { getPosts } from '../actions'
class Posts extends Component {
handleButtonClick() {
this.props.dispatch(getPosts({userId: 1}))
}
render() {
const {posts} = this.props.fetch
return (
<div className="container">
<h2>Posts</h2>
{posts.map((post) => (
<PostItem key={post.id} post={post} />
))}
<button className="btn btn-default" onClick={this.handleButtonClick.bind(this)}>Push</button>
</div>
)
}
}
const select = state => (state)
export default connect(select)(Posts)
edit devServer.js
const webpack = require('webpack')
const browserSync = require('browser-sync')
const proxy = require('http-proxy-middleware')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const historyApiFallback = require('connect-history-api-fallback')
const config = require('./webpack.config')
const bundler = webpack(config)
const apiProxy = proxy('/api', {
pathRewrite: {'^/api' : ''},
target: 'https://jsonplaceholder.typicode.com',
changeOrigin: true,
logLevel: 'debug'
})
browserSync({
open: false,
server: {
baseDir: config.output.path,
index: "index.html",
middleware: [
apiProxy,
webpackDevMiddleware(bundler, {
publicPath: config.output.publicPath,
stats: { colors: true }
}),
webpackHotMiddleware(bundler),
historyApiFallback()
]
},
files: [
'www/*.html'
]
})
Implement it and click button.
% node devServer.js
説明
非同期での処理の場合はイベントの発生を FETCH_REQUESTED, FETCH_SUCCESSED, FETCH_FAILED とします。 こうすることで WEB API への通信が開始してから完了するまで loading bar を表示したり、通信の成否に応じて Notification を表示することができます。
まとめ
今回は redux による非同期処理について触れました。 次回は react router について触れます。