LoginSignup
2

More than 5 years have passed since last update.

redux-saga ! React

Last updated at Posted at 2018-05-24

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;
  }
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2