バカのためのredux-saga入門

React + Reduxを使用していると、避けて通れない悩みが非同期通信のコードをどこに書くのかということです。

何も知らない初心者は、コンポーネントの中に非同期通信でデータを取得するコードを書いてしまいがいちですが、コンポーネントの再利用が難しくなり、どこで通信を開始してるのか分かりづらくコードの見通しが悪くなります。

ですので、非同期通信部は、コンポーネントの外の何処かに書いてやって、親のコンポーネントからコンポーネントに与えてやるようにすれば良いというのが一応の解答になるでしょう。

で、この外側からコンポーネントにデータを与えてやるというのがReduxの基本的な考え方であるわけで、Reduxを使っていれば非同期通信もReduxの中でやるのが自然であるわけです。

じゃあReduxで非同期通信をやるには、どうすればいいのだと調べていくとどうやら、Redux単体では非同期通信はできないらしい。

その結果、redux-thunkというライブラリを使用することになります。なぜなら検索すると一番ヒットするからだ。しかしながら、redux-thunkは一度書いたらわかりますがコードがグッチャグチャになります。

グッチャグチャにならない書き方もあるのかもしれませんが、公式のチュートリアルだとグッチャグチャになるので、良識あるプログラマなら違うライブラリを探すハメになるでしょう。そしてredux-sagaというライブラリに出会うハズです…

長くなりましたが、ここからが本題。

どうやらredux-sagaを使えば非同期通信のコードをもっと分割していけるらしい。

が、検索してQiitaの記事に辿り付いてコードをみてもforkとかfunctionに*が付いてたりしてよく分からん。理解する気も起きない。

細々とした動作なんてどうでもいいのです。コピペでオレのプロジェクトに貼って、ちょっと修正すれば動くようなコードを用意してくれ、という不届きなプログラマのために用意しました。

github.com

このプロジェクトをクローンしてnpm startコマンドを実行すると動きます。

sagas.js

import fetch from 'isomorphic-fetch';
import { receivePosts, REQUEST_POSTS } from './actions';
import { call, fork, takeLatest, put } from 'redux-saga/effects';

function fetchCityCodes(prefCode) {
  const URL = `http://www.land.mlit.go.jp/webland/api/CitySearch?area=${prefCode}`;
  return fetch(URL)
    .then(response => response.json())
    .then(json => {
      const data = json.data;
      return { data };
    });
}

function* fetchPostsIfNeeded(action) {
  const { data } = yield call(fetchCityCodes, action.prefCode);
  yield put(receivePosts(data));
}

export default function* rootSaga() {
  yield takeLatest(REQUEST_POSTS, fetchPostsIfNeeded);
}

このrootSagaっていうところで「REQUEST_POSTS」っていうアクションが発行されたら、functionを呼び出して…という流れが基本的なものです。「function*」っていうのはジェネレータファンクションっていう再入可能なやつらしいです。

configureStore.js

import { createStore, applyMiddleware } from 'redux'
import logger from 'redux-logger'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducers'
import rootSaga from './sagas';

export default function configureStore() {
  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(
    rootReducer,
    applyMiddleware(
      sagaMiddleware,
      logger
    )
  );
  sagaMiddleware.run(rootSaga);

  return store;
}

storeを作る部分はこんな感じで書くらしいです。

あとの設定はreduxと一緒。 とりえあず、自分のプロジェクトなりでコピペして動くかどうか試してみて、感触が掴めたらこちらredux-sagaで非同期処理と戦う - Qiitaを読んでいくのが良いのではないかと思われます。

以上、宜しくお願い致します。