LoginSignup
32
28

More than 5 years have passed since last update.

reduxの非同期アクションをジェネレーター関数で処理するredux-sagaを試す

Last updated at Posted at 2016-05-09

redux-sagaが盛り上がってるらしいので触ってみました。

yelouafi/redux-saga: An alternative side effect model for Redux apps

redux-sagaはreduxにおける非同期アクションをジェネレーター関数を用いて、同期なスタイルで扱うことができるミドルウェアです。
公式に日本語のドキュメントもあります。
翻訳してくれた@kuyさんに感謝です。

redux-saga/README_ja.md at master · yelouafi/redux-saga

詳しい日本語の解説記事はこれから出てくることを期待して、とりあえず触ってみます。
reactと一緒に使うことがほとんどだと思いますが、最小構成で試したいのでreduxとredux-sagaのみでnode 6.1.0にて試してみました。

インストール

$ npm init -y
$ npm i -S redux redux-saga

reduxのみで雛形

カウンターのサンプルを書いてみます。

まずはreduxのみで基本形を書いてみます。

index.js
const {createStore} = require('redux');

function counter(state = 0, action) {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

const store = createStore(counter);

store.subscribe(() => {
    console.log(store.getState());
});

const action = type => store.dispatch({type});

action('INCREMENT');
action('INCREMENT');
action('INCREMENT');
action('DECREMENT');
$ node index.js
1
2
3
2

redux-sagaによる非同期処理

ここに1秒後にstateを1つ増やすINCREMENT_ASYNCを追加してみます。

index.js
'use strict';
const {createStore, applyMiddleware} = require('redux');
const reduxSaga = require('redux-saga');
const createSagaMiddleware = reduxSaga.default;
const {takeEvery, delay, effects} = reduxSaga;
const {put} = effects;

function counter(state = 0, action) {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

function * incrementAsync() {
    yield delay(1000);
    yield put({type: 'INCREMENT'});
}

function * watchIncrementAsync() {
    yield * takeEvery('INCREMENT_ASYNC', incrementAsync);
}

const sagaMiddleware = createSagaMiddleware();

const store = createStore(
    counter,
    applyMiddleware(sagaMiddleware)
);

sagaMiddleware.run(watchIncrementAsync);

store.subscribe(() => {
    console.log(store.getState());
});

const action = type => store.dispatch({type});

action('INCREMENT_ASYNC');
action('INCREMENT');
action('INCREMENT');
action('INCREMENT');
action('DECREMENT');
action('INCREMENT_ASYNC');
$ node index.js
0
1
2
3
2
2
3
4

少しわかりづらいですが、INCREMENT_ASYNCアクションが2回発行されたので最終的にstateが4になっています。
正しく動作しているようです。

簡単な解説をします。
redux-sagaがジェネレーター関数で非同期処理を同期処理に処理できます。
delayで1秒待ち、putでアクションを発行できます。
takeEveryはINCREMENT_ASYNCアクションがきたら、並列でincrementAsyncを処理します。

他のreduxのミドルウェアと同様にapplyMiddlewareでsagaミドルウェアを登録し、runでsagaを走らせます。

複数のsagaの追加

今のままだとロガーが心もとないのでredux-logger風のロガーを作ってみます。
アクションとアクション発行前と発行後のstoreの状態を表示してみます。

index.js
'use strict';
const {createStore, applyMiddleware} = require('redux');
const reduxSaga = require('redux-saga');
const createSagaMiddleware = reduxSaga.default;
const {takeEvery, delay, effects} = reduxSaga;
const {put, take, select, fork} = effects;

function counter(state = 0, action) {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

function * incrementAsync() {
    yield delay(1000);
    yield put({type: 'INCREMENT'});
}

function * watchIncrementAsync() {
    yield * takeEvery('INCREMENT_ASYNC', incrementAsync);
}

function * watchAndLog() {
    while (true) {
        console.log('before', yield select());
        const action = yield take('*');
        console.log('action', action);
        console.log('after ', yield select());
    }
}

function * rootSaga() {
    yield fork(watchAndLog);
    yield fork(watchIncrementAsync);
}

const sagaMiddleware = createSagaMiddleware();

const store = createStore(
    counter,
    applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(rootSaga);

const action = type => store.dispatch({type});

action('INCREMENT_ASYNC');
action('INCREMENT');
action('INCREMENT');
action('INCREMENT');
action('DECREMENT');
action('INCREMENT_ASYNC');
$ node index.js
before 0
action { type: 'INCREMENT_ASYNC' }
after  0
before 0
action { type: 'INCREMENT' }
after  1
before 1
action { type: 'INCREMENT' }
after  2
before 2
action { type: 'INCREMENT' }
after  3
before 3
action { type: 'DECREMENT' }
after  2
before 2
action { type: 'INCREMENT_ASYNC' }
after  2
before 2
action { type: 'INCREMENT' }
after  3
before 3
action { type: 'INCREMENT' }
after  4
before 4

INCREMENT_ASYNCアクションが2回発行されているので、最後にINCREMENTアクションが2回発行され、stateが増えるのがわかりますね。

さて、take, select, forkといった関数が出てきました。
takeはアクションを待ち受けることができます。
今回はすべてのアクションを待っていますが、take('アクション')で特定のアクションがきたときに作用する関数を簡潔に書くことができます。
forkはsagaを並列に実行し、selectはstateを取得できます。

まとめ

redux-sagaはジェネレーター関数によって非常にシンプルに非同期処理を書くことができます。
redux-thunkと使う状況であれば、これからは間違いなくredux-sagaを使うと思います。

今回のコードはgithubに公開しています。
間違った使い方をしてる等あれば、アドバイスをくださると嬉しいです。

akameco/redux-saga-example

参考

yelouafi/redux-saga: An alternative side effect model for Redux apps
redux-saga/README_ja.md at master · yelouafi/redux-saga
akameco/redux-saga-example

32
28
0

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
32
28