Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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
りあクト!

goemp
落語(立川流)/さくらももこ/横尾忠則/Golang/Ruby/React/爆笑問題/鬼越トマホーク
dmmcom
総合エンタテイメントサイト「DMM.com」を運営。会員数は2,900万人を突破。動画配信、FX、英会話、ゲーム、太陽光発電、3Dプリンタなど40以上のサービスを展開。沖縄での水族館事業参入、ベルギーでのサッカークラブ経営など、様々な事業を手掛ける。また2018年より若手起業家の支援を強化、「DMM VENTURES」による出資や、M&Aなどを積極的に展開している。
https://dmm-corp.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away