【React】ReduxToolkitで非同期処理を実装する


概要

  • Reduxを使って非同期処理を実装する際はReduxThunkのようなmiddlewareを使うケースが多いと思います
  • しかしmiddlewareを使うと実装がややこしくなってしまうケースも多く悩ましい場面もあります
  • ReduxToolkitはReduxThunkが内包されていて実装量少なくReduxで非同期処理を作ることができます

事前準備

npx create-react-app redux-thunk-sample
  • 以下のコマンドで起動できます
yarn start
# open http://localhost:3000

ReduxToolkitによる非同期処理の実装

  • 今回はQiitaのAPIをたたいて記事一覧を取得してみます

ライブラリの追加

  • Redux関連ライブラリをインストール
yarn add react-redux @reduxjs/toolkit

通信処理の作成

  • QiitaのAPIを叩く部分を作成します
src/api/qiitaApi.js
export async function getItems() {
  const res = await fetch('https://qiita.com/api/v2/items');
  const json = await res.json();
  if (!res.ok) throw new Error(json.message);
  return json;
}
  • 今回のメインとなるReduxの処理を書くファイルを作成します
src/store/qiitaSlice.js
import { createSlice } from '@reduxjs/toolkit';
import { getItems } from '../api/qiitaApi';

// Slice
export const qiitaSlice = createSlice({
  name: 'qiita',
  // stateの初期値を設定
  initialState: { loading: false, error: null, items: [] },
  reducers: {
    // 通信を開始した時に呼ぶ関数
    fetchStart(state, action) {
      state.loading = true;
      state.error = null;
    },
    // 通信が失敗した時に呼ぶ関数
    fetchFailure(state, action) {
      state.loading = false;
      state.error = action.payload;
    },
    // 通信が成功した時に呼ぶ関数
    fetchSuccess(state, action) {
      state.loading = false;
      state.error = null;
      state.items = action.payload;
    },
  },
});

// Actions
export const { fetchStart, fetchFailure, fetchSuccess } = qiitaSlice.actions;

// 外部からはこの関数を呼んでもらう
export const fetchItems = () => async dispatch => {
  try {
    dispatch(fetchStart());
    dispatch(fetchSuccess(await getItems()));
  } catch (error) {
    dispatch(fetchFailure(error.stack));
  }
};

// Selectors
export const selectQiita = ({ qiita }) => qiita;

// Reducer(must be default export)
export default qiitaSlice.reducer;
  • 通信中かどうかを示すloading、エラー情報を入れるerror、Qiitaから取得した記事を入れるitemsの3つのStateを持ちます
  • 非同期ではない場合はreducersに定義した関数を外部からdispatchしていました
    • 非同期処理の場合は処理の開始時と終了時にStateを更新したくなります
    • なので外部から直接reducersに定義した関数をdispatchせずにワンクッション挟む関数(ここではfetchItems)をdispatchしてもらうことで対応できます

記事一覧を表示するコンポーネントの作成

  • Reduxの処理を呼び出して取得した情報を表示するコンポーネントを作成します
src/components/QiitaItems.js
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { selectQiita, fetchItems } from '../store/qiitaSlice';

function QiitaItems() {
  const dispatch = useDispatch();
  const { loading, error, items } = useSelector(selectQiita);

  useEffect(() => {
    // fetchItemsを実行
    dispatch(fetchItems());
  }, [dispatch]);

  if (loading) return <p>...loading</p>;
  if (error) return <p>{error}</p>;

  return items.map(item => (
    <div key={item.id}>
      <h2><a href={item.url}>{item.title}</a></h2>
      <p>{item.created_at} by {item.user.id}</p>
    </div>
  ));
}

export default QiitaItems;
  • useEffectでfetchItemsをdispatchしているのでコンポーネント生成時に通信処理を呼び出している感じです
  • 通信中は...loadingと表示されて完了すると記事の情報が表示されるようにしています

Reduxのセッティング

  • Reduxを使うための設定を入れておきます
  • App.jsの修正
src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import QiitaItems from './components/QiitaItems';

function App() {
  return (
    <Provider store={store}>
      <QiitaItems />
    </Provider>
  );
}

export default App;
  • Storeの作成
src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import qiitaReducer from './qiitaSlice';

export const store = configureStore({
  reducer: {
    qiita: qiitaReducer,
  },
});

動作確認

  • うまくいっていれば以下のようになっているはずです

まとめ

  • ReduxToolkitを使うと通信処理もそれほど複雑さを感じずに実装できました
  • 実装されたコードもわかりやすくていいですね