41
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Reduxで非同期処理をしたいときに、なぜMiddlewareを使わないといけないのか

Last updated at Posted at 2017-01-17

Reduxで非同期処理をする場合は、redux-thunkredux-sagaなどのMiddlewareを使いましょうと書いているサイトは多数あります。
しかし、そもそも「なんでMiddlewareを使わないといけないの?」という根底が理解できなくてハマってしまいました。

そこで、同じことで悩んでいる人がいれば手助けになるのではと思い書いていきます。

※個人の考えが入っており間違ってる可能性が大いにありますので、その場合は指摘していただければうれしいです!!

#使用コード
http://qiita.com/enshi/items/19b1924b72f8c2ffd1eb
で書いたコードを使っていきます。

簡単に説明すると、入力した数字を1.08倍するだけのアプリケーションです。
tax.gif

また、非同期の処理はjqueryで「 https://httpbin.org/delay/2 」にアクセスして得るものとします。
※このURLは2秒後にレスポンスをjson形式で返してくれるページ。
※今回は慣れているjqueryを使いますが、Reactでは使わない機能が多いので、実際に作るときにはより軽いライブラリを使うと思います。

#Middlewareを使わずにActionCreaterに非同期処理を書けないのか
##jqueryを使って適当に書いてみる

ajax処理を行い、完了したら入力値に100を足して返す関数
import $ from 'jquery';

// ActionCreator
const ADDTAX = 'ADDTAX';

function addTax(price) {
  $.ajax({
    url: "https://httpbin.org/delay/2",
    success: function(data) {
      price = Number(price) + 100;  /* 入力値に100を足す */
      console.log(price);           /* コンソールログに表示させる */
    },
    error: function(data) {
    }
  });
  return {
    type: ADDTAX,
    price
  }       /* Actionを返す */
}

###結果
3240(30001.08)が表示され、
2秒後にconsole.logは動いた(3100と表示された)が、
Reducerは動かなかった(3100
1.08=3348が表示されない)
action1.gif

##同期処理にしてみる
async: falseをつけて同期処理にする

同期処理
import $ from 'jquery';

// ActionCreator
const ADDTAX = 'ADDTAX';

function addTax(price) {
  $.ajax({
    url: "https://httpbin.org/delay/2",
    async: false,             ←同期
    success: function(data) {
      price = Number(price) + 100;  /* 入力値に100を足す */
      console.log(price);           /* コンソールログに表示させる */
    },
    error: function(data) {
    }
  });
  return {
    type: ADDTAX,
    price
  }       /* Actionを返す */
}

###結果
計算ボタンを押してから2秒後に、
console.logが動く(3100と表示された)と同時に、
Reducerが動いた(3100*1.08=3348が表示された)
action3.gif

##結論
ActionCreatorでは、非同期処理した結果を返すことができない。
※むりやり同期処理させれば、(それがいいかはさておき)結果を返すことはできる。

#ActionCreatorがだめなら、Reducerに非同期処理を書けないのか
##jqueryを使って適当に書いてみる

import $ from 'jquery';

// Reducer
function appReducer(state, action) {
  switch (action.type) {
    case 'ADDTAX':
      $.ajax({
        url: "https://httpbin.org/delay/2",
        success: function(data) {
          action.price = Number(action.price) + 100;  /* 入力値に100を足す */
          console.log(action.price);           /* コンソールログに表示させる */
        },
        error: function(data) {
        }
      });
      return (
        Object.assign({}, state, {price: action.price * 1.08})
      );
    default:
      return state
  }
}

###結果
3240(30001.08)が表示され、
2秒後にconsole.logは動いた(3100と表示された)が、
Reducerは動かなかった(3100
1.08=3348が表示されない)
action4.gif

##同期処理にしてみる

同期処理
import $ from 'jquery';

// Reducer
function appReducer(state, action) {
  switch (action.type) {
    case 'ADDTAX':
      $.ajax({
        url: "https://httpbin.org/delay/2",
        async: false,              ←同期
        success: function(data) {
          action.price = Number(action.price) + 100;  /* 入力値に100を足す */
          console.log(action.price);           /* コンソールログに表示させる */
        },
        error: function(data) {
        }
      });
      return (
        Object.assign({}, state, {price: action.price * 1.08})
      );
    default:
      return state
  }
}

###結果
計算ボタンを押してから2秒後に、
console.logが動く(3100と表示された)と同時に、
Reducerが動いた(3100*1.08=3348が表示された)
action5.gif

##結論
Reducerでも非同期処理した結果を返すことができない。
※むりやり同期処理させれば、(それがいいかはさておき)結果を返すことはできる。

#コンテナに非同期処理を書けないのか
##jqueryを使って適当に書いてみる

コンテナで非同期処理を書く
import $ from 'jquery';

function mapDispatchToProps(dispatch) {
  return {
    onClick(price){
      $.ajax({
        url: "https://httpbin.org/delay/2",
        success: function(data) {
            var price2 = Number(price) + 100;
            console.log(price2);
            dispatch(addTax(price2));    /* 入力値に100を足したものをActionCreatorに渡す */
        },
        error: function(data) {
        }
      });
      dispatch(addTax(price));
    }
  };
}

###結果
3240(30001.08)が表示され、
2秒後にconsole.logは動き(3100と表示された)、
さらにReducerが動いた(3100
1.08=3348が表示された)

つまり、非同期処理ができた。
action6.gif

##コンテナでやれば非同期処理ができるのになんでMiddlewareを入れる必要があるのか
ありがたいことに、stackoverflowの該当しそうな記事を翻訳してくれている人がいました。
http://qiita.com/hachijirou/items/b9633e1dc7d2058d7528

要約すると以下のとおりです。

  1. 同じ処理をさせたい全てのmapDispatchToPropsに同じコードを書かなければならない。
  2. 複数回実行させた場合、1回目の非同期処理が終了する前に2回目が実行されてしまうと不具合が起きてしまう。

1は大規模なアプリケーションになったときなど、コンテナが複数ファイル存在する可能性がある。
その際に、同じAPIを複数の場所に書くことになってしまうと言っているのだと思います。
自作関数を集めた専用のファイルに書けばいいのではとも考えましたが、それだったらActionCreatorに書いたほうがわかりやすい。

2は2回目の実行時に行った処理が、1回目の非同期処理の完了時の処理に影響してしまうということを言っているのだと思います。
Middlewareを使用しActionCreatorに書くことによって、ActionCreatorを必ず通るので、実行時にidをインクリメントし処理内で同じidを使うことで見分けているようです。
これはstoreにstateを追加すればできそうですが、逆に言えばstateを追加する必要があり、コードが複雑になる可能性があります。

#結論
「APIを共通化できるから、Middlewareを使ったほうが便利だしコードもきれいになるよ」と言っているのだと感じました。

ただ、小規模なアプリケーションの場合などでMiddleware独自の書き方を1から勉強するぐらいだったら、コンテナやAPI発行する関数を集めたファイルに書くという手もありだと思います。

実際にMiddlewareを使わずにやってる人もいるようですし。
http://qiita.com/cacarrot/items/3e4a8ab7d8c058c1dfe3
http://qiita.com/193/items/7ff650616dd8f34804f4

#参考にしたサイト
http://qiita.com/hachijirou/items/b9633e1dc7d2058d7528
http://qiita.com/cacarrot/items/3e4a8ab7d8c058c1dfe3
http://qiita.com/193/items/7ff650616dd8f34804f4
http://qiita.com/yuinchirn/items/286353f5c80cc44f1caf
http://qiita.com/kuy/items/869aeb7b403ea7a8fd8a

41
29
4

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
41
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?