React tutorial (4)
あらすじ
前回は 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 について触れます。