LoginSignup
18
17

More than 3 years have passed since last update.

Reactでredux-sagaで非同期通信 〜redux-thunkとの比較も〜

Last updated at Posted at 2019-07-28

Reactでの非同期通信処理の方法

Reduxのミドルウェアを使って実装します

※ Reactのコンポーネント内で処理する方法もありますが、コンポーネントと通信処理が密結合になってコンポーネントのソースが複雑になり、可読性が下がるなどの理由で採用されるケースはあまりないためこの方法はとりません

redux-sagaとredux-thunkの比較

非同期通信処理の代表である redux-sagaとredux-thunkについて比較してみます

redux-thunk

redux-thunk_arch.png

上図のように、redux-thunkを導入すると、action creatorの中に非同期通信処理を書いたり、action creatorからさらに別のaction creatorをcallすることができるようになります。

そのため、本来の reduxのアーキテクチャの形から逸脱することになり、記述の自由度が上がり開発者各々が書くことでソースコードの可読性が下がってしまいます。

redux-saga

redux-saga_arch.png

・Actionが発行→ DispatcherはStoreとSagaのスレッドにActionを渡す
・非同期処理をTaskとして登録しておく。このTaskはアプリ起動時からスレッドのように実行待ちになる
・Actionが渡されるとWatch TaskはそれをキャッチしTaskを実行。実行結果をAction Creatorに渡す

ポイントは Reduxとは独立して動く

また、テストも書きやすくなっています。

スクリーンショット 2019-07-28 19.34.12.png
【↑ Redux-Sagaのロゴ。Reduxのロゴと独立していることを示している】

redux-saga redux-thunk
サイズ 14KB 352B
Githubスター数 18,450 12,778
記述量 多い 少ない
Action Creator 入れ子にならない 入れ子になるためコールバックが連なり可読性が下がる

redux-sagaの実装例

reduxの公式サンプルの counterを改造してボタンが押されてから1秒後にカウントを追加する非同期処理を実装していきます

参考
redux-saga official beginner tutorial
ソース

sagaを実行するために以下を実装します

  1. MiddleWareを作成
  2. 作成したMiddlewareを ReduxのStoreとconnectする

例)main.js

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import Counter from './Counter'
import reducer from './reducers'
import { helloSaga } from './sagas'

// sagaMiddlewareを作成
const sagaMiddleware = createSagaMiddleware()
// SagaMiddlewareをStoreとconnect
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)
// SagaMiddlewareを実行
sagaMiddleware.run(helloSaga)

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

こちらが実行するSaga(helloSaga)。 SagaMiddleware実行時に引数に渡しています
saga.js

export function* helloSaga() {
  console.log('Hello Sagas!')
}

非同期通信処理

クリックしてから1秒後に1カウントする処理を実行するボタンを追加
Counter.js

const Counter = ({ value, onIncrement, onDecrement, onIncrementAsync }) =>
  <div>
    <button onClick={onIncrementAsync}>
      Increment after 1 second
    </button>
    {' '}
   ...
  </div>

onIncrementAsync をStoreのactionにをconnectします
main.js

function render() {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() => action('INCREMENT')}
      onDecrement={() => action('DECREMENT')}
      onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
    document.getElementById('root')
  )
}

非同期通信をするために、 Taskと TaskWatcherを定義します

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

const delay = (ms) => new Promise(res => setTimeout(res, ms))

// ...

// worker Saga: 非同期のincrement taskを実行します
export function* incrementAsync() {
  yield delay(1000) // Generatorをとめる働きをします。
  yield put({ type: 'INCREMENT' })
}

// TaskWatcher: INCREMENT_ASYNCのたびにincrementAsync taskを新たに作成します
export function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}

actionのtypeが INCREMENT_ASYNC の場合に incrementAsync()を実行します

Effect API

put: Action Creatorを実行してActionをdispatchします
他にも、select, join, callなどがあります
参考:https://redux-saga.js.org/docs/api/

複数のSagaを同時に実行

sagas.js

export default function* rootSaga() {
  yield all([
    helloSaga(),
    watchIncrementAsync()
  ])
}

上で定義した rootSagaをmiddleware実行時に引数に渡すことで
それぞれのsagaが平行に実行されます

main.js

// ...
import rootSaga from './sagas'

const sagaMiddleware = createSagaMiddleware()
const store = ...
sagaMiddleware.run(rootSaga)

// ...

まとめ

reduxのアーキテクチャーをそのままに独立したものとして非同期処理を実装でき、redux-thunkと違い書き方が統一されるため、redux-sagaで非同期処理を実装するのがいいと思います。

参考

redux-saga officai doc
redux-saga official github
redux-saga README_ja

redux-thunk github
りあクト!

18
17
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
18
17