redux-saga
は React/Redux アプリケーションにおける副作用(データ通信などの非同期処理、ブラウザキャッシュへのアクセスのようなピュアではない処理)をより簡単で優れたものにするためのライブラリです。
Saga はアプリケーションの中で副作用を個別に実行する独立したスレッドのような動作イメージです。 redux-saga
は Redux ミドルウェアとして実装されているため、スレッドはメインアプリケーションからのアクションに応じて起動、一時停止、中断が可能で、Redux アプリケーションのステート全体にアクセスでき、Redux アクションをディスパッチすることもできます。
ES6 の Generator 関数を使うことで読み書きしやすく、テストも容易な非同期フローを実現しています(もし馴染みがないようであればリンク集を参考にしてみてください)。それにより非同期フローが普通の同期的な JavaScript のコードのように見えます(async
/await
と似ていますが Generator 関数にしかないすごい機能があるんです)。
これまで redux-thunk
を使ってデータ通信を行っているかもしれませんが、 redux-thunk
とは異なりコールバック地獄に陥ることなく、非同期フローを簡単にテスト可能にし、アクションをピュアに保ちます。
$ npm install --save redux-saga
または、
$ yarn add redux-saga
別の方法として、UMD ビルドを HTML ページの <script>
タグで直接使うこともできます。詳しくはこちら.
ボタンがクリックされたらリモートサーバから何らかのユーザデータを取得する UI を考えてみます(簡略化のため、起点となる部分のみ例示します)。
class UserComponent extends React.Component {
...
onSomeButtonClicked() {
const { userId, dispatch } = this.props
dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
}
...
}
コンポーネントはプレーンオブジェクトの Action を Store に送り出します。
USER_FETCH_REQUESTED
Action を監視して、ユーザデータ取得の API 呼び出しを実行する Saga を作ります。
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
// ワーカー Saga: USER_FETCH_REQUESTED Action によって起動する
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", user: user});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
/*
USER_FETCH_REQUESTED Action が送出されるたびに fetchUser を起動します。
ユーザ情報の並列取得にも対応しています。
*/
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
/*
代わりに takeLatest を使うこともできます。
しかし、ユーザ情報の並列取得には対応しません。
もしレスポンス待ちの状態で USER_FETCH_REQUESTED を受け取った場合、
待ち状態のリクエストはキャンセルされて最後の1つだけが実行されます。
*/
function* mySaga() {
yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}
export default mySaga;
定義した Saga を実行するには redux-saga
ミドルウェアを使って Redux の Store と接続する必要があります。
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// Saga ミドルウェアを作成する
const sagaMiddleware = createSagaMiddleware()
// Store にマウントする
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
// Saga を起動する
sagaMiddleware.run(mySaga)
// アプリケーションのレンダリング
dist/
ディレクトリには redux-saga
の umd ビルドもあります。
umd ビルドを使うときは window オブジェクトに ReduxSaga
という名前で redux-saga
が提供されます。
この方法を使用することでES6のimport
シンタックスを用いずにSagaミドルウェアを作成することができます。
var sagaMiddleware = ReduxSaga.default()
umd バージョンは webpack や browserify を使わない場合には便利です。unpkg から直接利用できます。
以下のビルドが利用可能です:
- https://unpkg.com/redux-saga/dist/redux-saga.umd.js
- https://unpkg.com/redux-saga/dist/redux-saga.min.umd.js
重要! ターゲットのブラウザが ES2015 の Generator をサポートしていない場合、babel のような有効な polyfill を提供しなければなりません。
polyfill は redux-saga の前にインポートされなければなりません。
import 'babel-polyfill'
// この後に
import sagaMiddleware from 'redux-saga'
$ git clone https://github.com/redux-saga/redux-saga.git
$ cd redux-saga
$ yarn
$ npm test
以下は Redux リポジトリから移植したサンプルです。
3つのカウンターのサンプルがあります。
ES2015を使っていない素の JavaScript と UMD ビルドを使用したデモです。すべてのソースコードは index.html
にインラインで埋め込まれています。
単純に index.html
をブラウザで開くだけでサンプルを実行できます。
重要 ご利用のブラウザが Generator をサポートしている必要があります。 最新の Chrome / Firefox / MS Edge であれば大丈夫です。
webpack と高レベル API takeEvery
を使用したデモです。
$ npm run counter
# サンプルのテストを実行
$ npm run test-counter
低レベル API を使ったタスクのキャンセルのデモです。
$ npm run cancellable-counter
$ npm run shop
# サンプルのテストを実行
$ npm run test-shop
$ npm run async
# サンプルのテストを実行
$ npm run test-async
$ npm run real-world
# まだテストはありません・・・
TypeScriptをRedux Sagaで使用する場合にはDOM.Iterable
かES2015.Iterable
が必要になります。
target
がES6
であればすでに設定されている可能性がありますが、ES5
の場合には手動で追加する必要があります。
tsconfig.json
ファイルと公式のコンパイラオプションドキュメントを確認してください。
様々な種類のRedux Saga公式ロゴがロゴディレクトリにあります。
Redux Sagaがジェネレータの代わりにasync/await
構文を使うように求めるいくつかのissueが提起されました。
私たちは今後もジェネレータを使用します。
async/await
の主要なメカニズムはPromiseであり、Promiseではスケジューリングの簡素さと既存のSagaのセマンティックを維持させることが難しいためです。
async/await
ではキャンセルといった特定の事項を許可しません。ですがジェネレータを使用すると作用をいつ、どのように実行するかを完全に制御できます。
毎月の寄付で私たちの開発を援助し、活動を継続できるように支援してください。 [支援者になる]
スポンサーとしてあなたのサイトへのリンクとなるロゴをGithub上のREADMEに掲載しませんか。 [スポンサーになる]