5
5

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.

React Hooks + Redux ToolKit (+ TypeScript + axios)でQiitaAPIをたたく(非同期処理)

Posted at

目的

React Hooks + Redux ToolKit (+ TypeScript + axios)を使った非同期処理の実装を行います。
今回はQiitaのユーザーの記事一覧取得APIを対象にします。
https://qiita.com/api/v2/docs
GET /api/v2/users/:user_id/items

また、Redux Toolkitの詳しい説明はこちらの記事で紹介されているので是非参考にしてみてください。

参照:Redux ToolKit 公式サイト

先に動作イメージを確認する方はこちら

環境構築

今回はRedux + TypeScript のテンプレートを使用します。

npx create-react-app my-app --template redux-typescript

作成したアプリにaxiosを追加します。

axiosをインストール
npm install axios@0.21.1

フォルダ構造は以下に示します(今回使用するフォルダのみ記載しています)。

my-app
 └ src
    ├ App.tsx
    ├ app
    │  └ store.js
    └ features
       └ qiita
          │ Qiita.tsx
          └ qiitaSlice.js

実装

実装手順として、qiitaSliceに非同期処理を実装し、そのreducerをstoreに登録します。
その後、storeに登録されているqiitaSliceの状態をQiitaコンポーネントで参照を行う動きになります。

Sliceの実装

qiitaSlice.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
import { RootState } from "../../app/store";

interface Item {
  rendered_body: string;
  body: string;
  coediting: boolean;
  comments_count: number;
  created_at: string;
  group: any;
  id: string;
  likes_count: number;
  private: boolean;
  reactions_count: number;
  tags: {
    name: string;
    versions: [];
  }[];
  title: string;
  updated_at: string;
  url: string;
  user: any;
  page_views_count: null | number;
  team_membership: any;
}

interface Items {
  items: Item[];
}

const initialState: Items = {
  items: [],
};

/** データ取得非同期処理 */
// 投稿ーデータを取得
export const fetchAsyncGetUserItems = createAsyncThunk(
  "qiita/getUserItems",
  async (userName: string) => {
    const { data } = await axios.get<Item[]>(
      `https://qiita.com/api/v2/users/${userName}/items`
    );
    return data;
  }
);

/** Slice */
const qiitaSlice = createSlice({
  name: "qiita",
  initialState: initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchAsyncGetUserItems.fulfilled, (state, action) => {
      return {
        ...state,
        items: action.payload,
      };
    });
    builder.addCase(fetchAsyncGetUserItems.rejected, (state, action) => {
      return {
        ...state,
        items: initialState.items,
      };
    });
  },
});

// 各コンポーネントからstateを参照できるようにエクスポートをしておく
export const selectItems = (state: RootState) => state.qiita.items;

export default qiitaSlice.reducer;

今回の肝となるSliceの実装になります。1つずつ見ていきましょう。

  • まず、前半のinterface定義はAPIの戻り値の型になります。

  • 次に非同期処理部分ですが、これはSliceの生成とは別に関数として定義します。

export const fetchAsyncGetUserItems = createAsyncThunk(
  "qiita/getUserItems",
  async (userName: string) => {
    const { data } = await axios.get<Item[]>(
      `https://qiita.com/api/v2/users/${userName}/items`
    );
    return data;
  }
);

この時使用されるのがredux-toolkitで用視されている非同期要求関数createAsyncThunkになります。
第一引数に任意の非同期処理名、第二引数に非同期ロジックのコールバック関数を渡します。
今回はQiitaのユーザーの記事一覧取得APIを対象にしていますが、ユーザー名だけ引数として受け取るようにしています。

  • そしてSliceを作成している部分が以下になります。
    ここではSliceの名前、保持するstateの初期値、Stateに対して許可する更新処理を定義していきます。
const qiitaSlice = createSlice({
  name: "qiita",
  initialState: initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchAsyncGetUserItems.fulfilled, (state, action) => {
      return {
        ...state,
        items: action.payload,
      };
    });
    builder.addCase(fetchAsyncGetUserItems.rejected, (state, action) => {
      return {
        ...state,
        items: initialState.items,
      };
    });
  },
});

こちらのextraReducersに非同期処理のパターンをaddCaseで追加します。
ちなみに定義できるのは以下の3種類となります。

  • pending: 保留中
  • fulfilled: 成功
  • rejected: 失敗

今回はAPIリクエストが成功した場合は戻り値でstateを更新するコールバック関数を、
失敗した場合は初期値でstateを更新するコールバック関数を定義しています。

最後にstoreに登録するreducerと、登録後に各コンポーネントからstateの値を参照するための関数をエクスポートしておきます。

// 各コンポーネントからstateを参照できるようにエクスポートをしておく
export const selectItems = (state: RootState) => state.qiita.items;

export default qiitaSlice.reducer;

このstate.qiita.itemsqiitaにあたる部分はこの後実装するstoreへの登録名になります。

以上でSliceの実装は完了です。
ここまでくればあと少しです!!

storeの実装

上記の createSlice で作成した各 Reducer を一つにまとめ、ストアを生成します。
環境構築の手順通りにアプリを作成した場合、storeのテンプレートはすでに作成済みかと思います。
今回追加した部分にはコメントを載せています。

store.ts
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import qiitaReducer from "../features/qiita/qiitaSlice"; // 追加

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

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

以上でstoreの実装は完了です。

コンポーネントの実装

最後にSliceで管理されているstateの参照や非同期処理の実行を定義します。

Qiita.tsx
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchAsyncGetUserItems, selectItems } from "./qiitaSlice";

const Qiita: React.FC = () => {
  const itemsData = useSelector(selectItems);
  const dispatch = useDispatch();

  useEffect(() => {
    // 非同期処理を実行 // 引数に例として私のQiitaアカウント名を指定
    dispatch(fetchAsyncGetUserItems("eiji-noguchi"));
  }, [dispatch]);

  const items = itemsData.map((item) => (
    <div key={item.id}>
      <p>{item.title}</p>
      <a href={item.url}>{item.url}</a>
    </div>
  ));

  return <>{items}</>;
};

export default Qiita;

コンポーネント内からはuseSelectorをつかってstateの値を参照しています。
また、useDispatchを使い非同期処理を実行しています。ここであuseEffectと合わせて、最初の表示時に非同期処理を呼ぶようにしています。

ついでに取得したユーザーの記事のタイトル一覧を画面に表示してみましょう。
作成したQiitaコンポーネントをApp.tsxに埋め込みます。

App.tsx
import "./App.css";
import Qiita from "./features/qiita/Qiita";

function App() {
  return (
    <div className="App">
      <Qiita />
    </div>
  );
}

export default App;

以上で実装は完了になります。

アプリ実行

npm start

で実行してみましょう。こんな画面が出ればOKです。

image.png

まとめ

redux-toolkitを使った非同期処理を実装できるようになりました。
複雑になりがちな状態管理を定まったルール内で使えるようになるredux-toolkitはこれからのReact開発に必須なライブラリになると思われます。
正直、最初からすべてを理解するのは大変ですが、慣れるとReact開発において強力な一手になること間違いなしですね。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?