【私の学習メモ】Redux-Saga

こんにちは、ワタナベ(wtnb_dev)です。

今回は、Redux-Sagaについての学習メモです。

注意事項

基本的に自分用のメモなので余計な記載は省いてます。参考にされる場合はファイル名などを適宜変更するようお願いします。

はじめに

主に非同期通信などの副作用処理を書きやすくするためのライブラリ。
Reduxのミドルウェアとして使える。

使い方

※Todoアプリの開発を想定。

インストール

yarn add redux-saga

sagas.jsの作成

import {call, put, takeEvery} from 'redux-saga/effects';
import Api from '../services/index';
import { FETCH_TODO_LIST, fetchTodoListSucceeded, fetchTodoListFailed } from '../actions';

function* fetchTodoList(action) {
 try {
   const todoList = yield call(Api.fetchTodoList);
   yield put(fetchTodoListSucceeded(todoList));
 } catch (e) {
   yield put(fetchTodoListFailed(e.message));
 }
}

function* mySaga() {
 yield takeEvery(FETCH_TODO_LIST, fetchTodoList);
}

export default mySaga;

mySagaをreduxのミドルウェアとして登録

import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux';
import createSagaMiddleware from 'redux-saga';
import {Provider} from 'react-redux';
import reducers from './reducers/index';
import mySaga from './sagas/sagas';
import App from './App';

const sagaMiddleware = createSagaMiddleware();
const store = createStore(
 reducers,
 applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(mySaga);

ReactDOM.render(
 <Provider store={store}>
   <App />
 </Provider>,
 document.getElementById('root')
);

あとは普通にdispatch

import React from 'react';
import {connect} from 'react-redux';
import { fetchTodoList } from './actions';

const App = props => {
 const {isFetching, todoList, message} = props;
 
 const handleFetchTodoList = () => {
   const {dispatch} = props;
   dispatch(fetchTodoList());
 }
 
 return (
   <div>
     <h1>Redux-Saga Sample</h1>
     {message && <p>エラー:{message}</p>}
     {isFetching && <p>読込中...</p>}
     <button onClick={handleFetchTodoList}>Todoリストの取得</button>
     <div>
       {todoList.map(todo => <p key={todo.id}>{todo.name}</p>)}
     </div>
   </div>
 );
}

export default connect(
 state => ({
   isFetching: state.todo.isFetching,
   todoList: state.todo.todoList,
   message: state.todo.message
 }))(App);

全ソース

https://github.com/uniurchin112/learn__redux-saga

takeEvery

Actionが同時に複数回来たときは並行してタスクを実行する。

takeLatest

最後のActionだけレスポンスを取得したい場合はtakeEveryの代わりにこっちを使う。まだ完了していないタスクはキャンセルする。

Effects

ミドルウェアに指示を出すためのプレーンオブジェクト。redux-saga/effectsにある関数を呼び出すことで生成できる。

function* fetchTodoList() {
 const todoList = yield Api.fetch('/todoList')
}

上のようなジェネレータ関数をテストするとき、以下のように書くと、nextの戻り値はApi.fetchの戻り値(= Promise)となり、比較が難しい。

const iterator = fetchTodoList()
assert.deepEqual(iterator.next().value, ??)

fetchTodoListでテストしたいのは、Api.fetchを意図した引数で呼び出せているかなので、以下のようなオブジェクトが得られればテストできる。

{
 CALL: {
   fn: Api.fetch,
   args: ['./todoList']
 }
}

このようなオブジェクトを取得するには、以下のように書き換える(callによるラップ)。

function* fetchTodoList() {
 const todoList = yield call(Api.fetch, '/todoList')
}

callはすぐにApi.fetchを実行せず、オブジェクトを返すのみ。このオブジェクトをredux-sagaミドルウェアが実行し、実行結果をジェネレータ関数に返して再開する。

関数呼び出しではなくdispatchする場合は、callの代わりにputを用いる。

function* fetchTodoList() {
 const todoList = yield call(Api.fetch, '/todoList')
 yield put({type: FETCH_TODO_LIST_SUCCEEDED, todoList});
}

おわりに(感想)

今のところ、redux-thunkより簡潔に書けるイメージ。


おわり!

この記事が気に入ったらサポートをしてみませんか?