3
3

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 3 years have passed since last update.

[振り返り] Redux ToolkitとRedux Thunkがチーム状況にあってなかった話

Last updated at Posted at 2021-07-17

Redux Toolkitがでてきて、だいぶReduxが使いやすくなったという評判ですね。

ただ、チームの状況が整っていない場合、
メンバーがReact経験が浅い場合、
必要性がなさそうな場合は、
Redux ToolkitとRedux Thunkを使ってAPIコールをするのはやめたほうが良さそうという記事です。

私のチームは、2021年上半期はRedux ToolkitとRedux Thunkにかなり苦しめられました。

そのときの状況

  • 既存サービスのリプレイスの受託案件。Next.jsを利用した大・中・小の3種類のフロントエンド開発と、サーバーサイド開発。
  • 納期がかなり迫っており、担当箇所を分けて、レビューや技術共有の時間をあまり取らないという方針であった。
  • プロジェクト(大)から中心に開発が進んでおり、Rudux ToolkitとRedux Thunkを使って、全データをfetchしていた。
  • メンバーのアサインがうまくいっておらず、React経験が少ないメンバーのみでの開発となった。(全員SPAの開発経験はあった。)
  • 納品後の保守・改修は、開発経験1~2年の後輩が担当予定だった(SPA開発経験は多くはない)

ちょっとツッコミどころがあるかもしれませんが、上記のような状況でした。

私はプロジェクトが始まってしばらくしてからアサインされ、
プロジェクト(小)を少し進めてから、プロジェクト(大)に入って一緒に進めることになりました。

アサイン当初から、APIでReduxを使う理由について議論をしたかったのですが、
議題にはしながらもその時間が十分に取れず、また修正もそれなりに発生するために、
とりあえず他の部分も、今あるものと同じように進めていこうということになりました。

開発を進めていってどうなったか

  • APIコール部分の処理をどうしても実装できないメンバーが2人でて、タスクの処理が進まなかった。
  • 私自身は進められないわけではないが、少しハマりながら進めていた。
  • 状態管理系の潜在バグがかなり入り込んだ。

結果、プロジェクト(大)の進捗が芳しくないという状況になりました。
API周りで全メンバーが苦しんでいました。

その反省もあり、プロジェクト(小)の開発は、
APIコール部分にRedux Tookit, Redux Thunkを使わずに進めることにしました。
結果、APIコール部分の実装は難易度を下げることができ、
また保守・改修担当予定だったメンバーからも扱いやすいといってもらえました。

Redux Thunkを使うと、Reduxがかなり書きやすくなるという意見は結構聞いたりします。
ただ、Redux ToolkitとRedux Thunkを使うと、Axiosだけを使ったコードより複雑で、
ある程度の土台がないと苦しいです。

実際にコードをそれぞれ見てみましょう。

Redux Toolkit, Redux Thunkを使ったAPIコールサンプル

  • storeにsliceを追加
  • createAsyncThunkでリクエスト情報を設定
  • createSliceでpending, fulfilled, rejectedのときの挙動を設定
  • apiコールのdispatch
  • sliceの呼び出し
    の5つの工程が必要になります。
    それぞれ以下のようなコードになります。

まずはstoreにsliceを追加します。

import { combineReducers } from 'redux';
import logger from 'redux-logger';
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import { sampleSlice, initialState as userSampleState } from './modules/Samples';


const rootReducer = combineReducers({
sampleState: sampleSlice.reducer,
});

const preloadedState = () => {
  return {
    sampleState: userSampleState,
  };
};

const store = () => {
  const middlewareList = [...getDefaultMiddleware(), logger];

  return configureStore({
    reducer: rootReducer,
    middleware: middlewareList,
    devTools: process.env.NODE_ENV !== 'production',
    preloadedState: preloadedState(),
  });
};

export default store;

次にcreateAsyncThunkでリクエスト情報を設定します。

api/Sample.ts
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from '../Common';

export const fetchChangedExaminationTimes = createAsyncThunk(
  'examination_times/fetchChangedExaminationTimes',
  async (arg: {}, thunkAPI) => {
    const { parameters } = arg;
    try {
      const url = `samples`;
      const response = await axios.get(url, {
        params: parameters,
      });
      return response.data;
    } catch (error) {
      ~~
    }
  },
);

次にcreateSliceでpending, fulfilled, rejectedのときの挙動を設定します。
とりあえずこの記事ではfulfilled(正常完了)時の処理だけ書きます。

module/Sample.ts
import { createSlice } from '@reduxjs/toolkit';
import { fetchSamples } from '../api/Sample';

export const sampleSlice = createSlice({
  name: 'samples',
  initialState: initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchSamples.fulfilled, (state, action) => {
      state.samples = action.payload.samples;
      state.error = false;
    });
  },
});

次にapiコールのdispatchとsliceの呼び出しです。
これは同じコンポーネントに書くときも、別のコンポーネントになるときもあると思います。

Sample.tsx
import React, { useEffect } from 'react';
import {fetchSamples} from 'Sample';

const Samples: React.FC = () => {  
  const state = useSelector(
    (state: {
      sampleState: SampleState;
    }) => state,
  );

  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchSamples({}));
  }, []);
  
  return <></>;
}

もっと簡潔な書き方があるかもしれませんが、
新規でAPIを追加しようとすると5箇所ほど編集する必要があります。
うまく進めることができなかったメンバーもしょうがないかなと思います。

また、私自身もcreateSliceとcreateAsyncThunkの引数がわかりづらくて辛いなと感じていました。
慣れてくるとやりやすいのかもしれませんが、APIコール以外のライブラリについても調査が必要だったので、
やはりReact経験の浅いチームには辛いなと思います。

Axiosだけを使ったAPIコールサンプル

状態管理をしていないので当たり前なのですが、
Axiosだけの方が、かなりシンプルになります。

呼び出し元と呼び出し先の2箇所に変更を加えれば、
型は別で定義が必要かもしれませんがとりあえず動きます。

Common.ts
import axios from 'axios';

const instance = axios.create(
  baseURL: 'http://example.com',
  timeout: 1000,
);

instance.interceptors.request.use((config) => {
  ~~
  return config;
});
export default instance;

Sample.ts
import axios from './Common';
 
export const fetchSamples = async ({parameter}): Promise<SampleInterface> => {
  try {
    const url = '/samples';
    const response = await axios.get(url, {
      params: parameter,
    });
    return response;
  } catch (error) {
    ~~
  }
};
SampleComponent.tsx
import React, { useEffect } from 'react';
import { fetchSamples } from 'Sample';

const Samples: React.FC = () => {
  useEffect(() => {
    (async () => {
      fetchSamples();
    })();
  }, []);
  
  return <></>;
}

ベースの部分がシンプルだと、新規でAPIコール処理を作る場合も簡単です。
エラーハンドリングをしたいとか、トーストを出したいとか必要なことは追加していきました。

なお、状態管理はuseStateとuseContextのみで進めました。
useContextは再レンダリングの懸念もありますが、
開発要件と使う必要のある箇所を考えると、特に問題にはなりませんでした。

APIコール部分以外でReduxを使って書くというやり方は、もしかしたら問題なかったかもしれません。
半年前の私たちのチームにマッチしなかったのは、APIコールにReduxを使うという点です。

今後、私のチームでは、API周りでReduxを取り入れないか、
ある程度メンバーの経験値がついている場合に取り入れるか、
React Queryなど他の手段使うか比較検討しようかなと思っています。

無理のなく学習コストを見積もって、チームでできることを増やして開発を進めていきたいなと思います。

3
3
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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?