redux-saga
reduxでは非同期処理どこに書けばいいの?問題。
この間のToDoリストシンプルなサンプルにSagaを適用してみる。
これをやっている↓
https://qiita.com/mpyw/items/a816c6380219b1d5a3bf#redux-saga-%E3%81%AE%E5%B0%8E%E5%85%A5
※APIはこれを準備した。
https://qiita.com/manipulative/items/d254ecf00aac8682d08b
github
react:https://github.com/ma-ak2011/ReactToDoList/tree/f3bee9dc49cdcc8d805e711b1a5460e904af5213
API:https://github.com/ma-ak2011/ToDoListAPI
react to redux to saga !
store作るところでMiddlewareにSagaを組み込む。
stores/stores.jsx
import {createStore, combineReducers, applyMiddleware} from 'redux';
import toDoListReducer from '../reducers/toDoList';
import { logger } from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas/sagas';
export default function configureStore(initialState) {
const sagaMiddleware = createSagaMiddleware();
const RootReducer = combineReducers({toDoListReducer});
const store = createStore(
RootReducer,
initialState,
applyMiddleware(
sagaMiddleware, logger
)
);
sagaMiddleware.run(rootSaga);
return store;
}
saga.js
import { delay } from 'redux-saga';
import { select } from 'redux-saga/effects';
import { call, put, fork, take } from 'redux-saga/effects';
import {
GET_TODOS,
successGetToDos,
errorGetToDos,
ADD_TODO,
successAdd,
errorAdd,
DELETE_TODO,
successDelete,
errorDelete
} from '../actions/toDoList';
import API from '../api/api';
function* handleGetToDos() {
while (true) {
const { payload } = yield take(GET_TODOS);
const { text, error } = yield call(API.getToDos, {});
if (text && !error) {
yield put(successGetToDos({ newToDoList: (JSON.parse(text)).UserList }));
} else {
yield put(errorGetToDos({ error }));
}
}
}
function* handleAddToDo() {
while (true) {
const { payload } = yield take(ADD_TODO);
const state = yield select();
const { text, error } = yield call(API.addToDo, { title: state.toDoListReducer.title, content: state.toDoListReducer.content});
const response = yield call(API.getToDos, {});
if (response.text && !response.error) {
yield put(successAdd({ newToDoList: (JSON.parse(response.text)).UserList }));
} else {
yield put(errorAdd({ error }));
}
}
}
function* handleDeleteToDo() {
while (true) {
const { payload } = yield take(DELETE_TODO);
const { text, error } = yield call(API.deleteToDo, payload);
const response = yield call(API.getToDos, {});
if (response.text && !response.error) {
yield put(successAdd({ newToDoList: (JSON.parse(response.text)).UserList }));
} else {
yield put(errorDelete({ error }));
}
}
}
export default function* rootSaga() {
yield fork(handleGetToDos);
yield fork(handleAddToDo);
yield fork(handleDeleteToDo);
}
api.js
import request from 'superagent';
function getToDos(payload) {
return new Promise((resolve, reject) => {
request
.get('http://localhost:65037/api/ReactToDoList/GetToDos')
.send({})
.end((err, res) =>{
if(err)
reject(err);
else
resolve(res);
});
});
}
function addToDo(payload) {
return new Promise((resolve, reject) => {
request
.post('http://localhost:65037/api/ReactToDoList/Add')
.type('form')
.send({ title: payload.title, content: payload.content })
.end((err, res) =>{
if(err)
reject(err);
else
resolve(res);
});
});
}
function deleteToDo(payload) {
return new Promise((resolve, reject) => {
request
.post('http://localhost:65037/api/ReactToDoList/Delete')
.type('form')
.send({ id: payload.id})
.end((err, res) =>{
if(err)
reject(err);
else
resolve(res);
});
});
}
export default { addToDo, deleteToDo, getToDos };
※ここからは割といつものredux
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './components/App';
import configureStore from '../js/stores/stores';
const store = configureStore();
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('root')
);
App.jsx
import React from 'react';
import { connect } from 'react-redux';
import InputToDo from '../containers/InputToDo';
import ToDoList from '../containers/ToDoList';
import { getToDos } from '../actions/toDoList';
class App extends React.Component{
constructor(props){
super(props);
}
componentDidMount(){
this.props.dispatch(getToDos());
}
render(){
return (
<div>
<InputToDo />
<ToDoList />
</div>
);
}
}
export default connect()(App);
components/InputToDo.jsx
const InputToDo = ({ title, content, changeTitle, changeContent, addToDo }) => {
return(
<div>
<div>
タイトル:<input type="text" value={title} onChange={e => changeTitle(e.target.value)}/>
<br/>
内容:<textArea value={content} onChange={e => changeContent(e.target.value)}/>
</div>
<div onClick={e => addToDo()}>追加</div>
</div>
);
};
export default InputToDo;
containers/InputToDo.jsx
import { connect } from 'react-redux';
import InputToDo from '../components/InputToDo';
import * as Actions from '../actions/toDoList';
import { bindActionCreators } from 'redux';
export default connect(
state => ({
title: state.toDoListReducer.title,
content: state.toDoListReducer.content
}),
dispatch => bindActionCreators(Actions, dispatch)
)(InputToDo);
components/ToDoList.jsx
const ToDoList = ({ toDoList, deleteToDo }) => {
return(
<ul>
{
toDoList.map((m, i) => {
return <li key={i}>
タイトル:{m.title}<br/>
内容:{m.content}<br/>
<button onClick={e => deleteToDo(m.id)}>削除{m.id}</button>
</li>;
})
}
</ul>
);
};
export default ToDoList;
containers/ToDoList.jsx
import { connect } from 'react-redux';
import ToDoList from '../components/ToDoList';
import * as Actions from '../actions/toDoList';
import { bindActionCreators } from 'redux';
export default connect(
state => ({
toDoList: state.toDoListReducer.toDoList
}),
dispatch => bindActionCreators(Actions, dispatch)
)(ToDoList);
サーバーとの通信が入った途端、Actionが急増した。
これは何とかしたい。
actions/toDoList.jsx
import { createAction } from 'redux-actions';
export const GET_TODOS = 'GET_TODOS';
export const ADD_TODO = 'ADD_TODO';
export const DELETE_TODO = 'DELETE_TODO';
export const CHANGE_TITLE = 'CHANGE_TITLE';
export const CHANGE_CONTENT = 'CHANGE_CONTENT';
//sagaからのActionType
export const SUCCESS_GET_TODOS = 'SUCCESS_GET_TODOS';
export const ERROR_GET_TODOS = 'ERROR_GET_TODOS';
export const SUCCESS_ADD_TODO = 'SUCCESS_ADD_TODO';
export const ERROR_ADD_TODO = 'ERROR_ADD_TODO';
export const SUCCESS_DELETE_TODO = 'SUCCESS_DELETE_TODO';
export const ERROR_DELETE_TODO = 'ERROR_DELETE_TODO';
export const getToDos = () => ({
type: GET_TODOS,
payload: {},
meta: {},
error: false
});
export const successGetToDos = createAction(SUCCESS_ADD_TODO);
export const errorGetToDos = createAction(ERROR_ADD_TODO);
export const addToDo = () => ({
type: ADD_TODO,
payload: {},
meta: {},
error: false
});
export const successAdd = createAction(SUCCESS_ADD_TODO);
export const errorAdd = createAction(ERROR_ADD_TODO);
export const deleteToDo = id => ({
type: DELETE_TODO,
payload: { id: id },
meta: {},
error: false
});
export const successDelete = createAction(SUCCESS_DELETE_TODO);
export const errorDelete = createAction(ERROR_DELETE_TODO);
export const changeTitle = title => ({
type: CHANGE_TITLE,
payload: { title: title },
meta: {},
error: false
});
export const changeContent = content => ({
type: CHANGE_CONTENT,
payload: { content: content },
meta: {},
error: false
});
reducerもかなり肥大化した。分離せねば。。
reducers/toDoList.jsx
import * as Actions from '../actions/toDoList';
const initialState = { title: '', content: '', toDoList:[], error:{} };
export default function toDoListReducer(state = initialState, action){
switch (action.type) {
case Actions.SUCCESS_GET_TODOS:
return Object.assign({}, state, {
toDoList: action.payload.newToDoList
});
case Actions.ERROR_GET_TODOS:
return Object.assign({}, state, {
error: action.error
});
case Actions.SUCCESS_ADD_TODO:
return Object.assign({}, state, {
toDoList: action.payload.newToDoList
});
case Actions.ERROR_ADD_TODO:
return Object.assign({}, state, {
error: action.error
});
case Actions.SUCCESS_DELETE_TODO:
return Object.assign({}, state, {
toDoList: action.payload.newToDoList
});
case Actions.CHANGE_TITLE:
return Object.assign({}, state, { title: action.payload.title });
case Actions.CHANGE_CONTENT:
return Object.assign({}, state, { content: action.payload.content });
default:
return state;
}
}