とみゾウ

概要だけ把握してコピペでプログラミングしたい

【React】 redux-saga でAPIを叩く

redux-sagaを利用してAPIを叩く際の実装内容のメモ。
理解した内容メモ付き。

環境

  • React (16.2.0)
  • redux (3.7.2)

今回通信に利用したライブラリ

  • axios (HTTPクライアント、実装簡単)

GitHub - axios/axios: Promise based HTTP client for the browser and node.js

事前知識

middlewareについて

reduxではaction→reducerの間にmiddlewareを挟み、共通処理を実行させることが可能。

Middleware - Redux

redux-sagaについて

非同期処理を簡単に記載するためのライブラリであり、 インターセプトするactionとそのとき実行させる関数をmiddlewareに登録するためのフレームワークを提供してくれる模様。 github.com

redux-sagaの導入

イメージ

f:id:k-tomoo:20180311201313p:plain 通常はActionの後にReducerが呼び出されるが、 redux-sagaを利用すると特定のActionが実行された場合に事前に登録しておいたジェネレータ関数を実行することができる。
これを利用し「適当なAction」と「APIを叩く処理」を登録しておくことでmiddlewareで通信処理を実行することが可能になる。

インストール

npm install --save redux-saga

yarn利用時は以下

yarn add redux-saga

APIを叩く関数を作成

import axios from 'axios';

// httpリクエストを送信するfunction
// 以下のようなjsonが返却される想定
// users: [{ id: 1, name : "hoge"}, { id: 2, name : "foo"}] 
function getUserList(page){
  return axios({
    method: 'get',
    url: '/api/users',
    params: {
      page,
    },
  })
    .then((results) => {
      const users = results.data.users;
      return { users };
    })
    .catch(error => (
      { error }
    ));
}

middlewareに登録するsaga effectを作成

redux-sagaでは以下のようなコマンド(関数)を提供してくれており、 これらを利用することで非同期処理が混ざっていたとしても処理を同期的に記載できるようになる。
(基本、コマンドの前にyieldをつけておけば  非同期処理はちゃんと実行完了するまで待って後続処理が実行されるはず  yieldの意味とかfunction*がわからなければ「ジェネレータ関数」でググると良い)

  • call: 引数で与えられた関数を実行し、Promiseの完了を待つ
  • put: actionをdispatchする
  • takeEvery: actionが実行されるたびに関数を実行する
  • takeLatest: 実行中の処理があったら中断し、新しく処理を開始する
  • all: 配列で指定されたeffectを並列で実行する

他にもいろんなコマンドがありますので興味があれば参照すると良いかも。
8. API Reference · Redux-Saga

import { put, call, takeEvery } from 'redux-saga/effects';

function* fetchUser(action) {
  const page = action.payload.page;
  // getUserList(上で作成したhttpリクエストする関数)を実行し、完了まで待つ
  const { users, error } = yield call(getUserList, page);
  if (users) {
    // 成功したので適当なactionをdispatchして画面更新
    yield put({type: "USER_FETCH_SUCCEEDED", user: user});
  } else {
    // todo: エラーハンドリング
  }
}
// 今回の例では「USER_FETCH_REQUESTED」のactionが実行されるたびに
// fetchUserを起動するようなeffectを作成
const saga = [takeEvery("USER_FETCH_REQUESTED", fetchUser)];

個別に作成したsaga effectを統合するための関数を作成

今回は1つだけなのであまり関係ないが、 実際のプロジェクトではいろんなファイルにsaga effectが定義されることになる。
それを1つのfunctionにまとめておく。

import { all } from 'redux-saga/effects';

function* rootSaga(){
  yield all([
    ...saga,
  ]);
}

StoreにSaga middleWareをマウント・起動

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

// USER_FETCH_REQUESTEDとかUSER_FETCH_SUCCEEDEDが実行された時のreducer
import reducer from './reducers'

// Saga ミドルウェアを作成する
const sagaMiddleware = createSagaMiddleware()

// Store にマウントする
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)
// すでに統合して1つにまとめておいたsaga effectを起動させる
sagaMiddleware.run(rootSaga)

以上でUSER_FETCH_REQUESTEDのactionを実行した時にuserの取得処理が走るはず

メリット

redux-sagaを使うと非同期処理を絡めて複数のactionの実行制御を簡単に書けるようになり、 またactionやreducerとは別の場所に処理を記載できるので処理の役割が明確になると思う。

  • action: どんなデータを利用するか
  • reducer: stateを変更
  • saga: 非同期処理や副作用を伴う一連の処理

参考

qiita.com