Axiosをコンポーネント化して使いやすくしたい(TypeScript)

APIをクライアントと結合するときによくAxiosを利用します。
今回Axiosを利用するにあたって共通化できるものは無いか考えました。
コピペで使えますので是非活用してみてください。   

# インストール
axiosのインストールが済んでい場合は実行してください。 
~~~command:コマンドプロンプト
$ npm install axios
~~~

# ディレクトリ構成

今回解説するディレクトリです。必要に応じて変更して下さい。

~~~tsx:ディレクトリ構成
---src
    |
    |---component 
    |      |---Apicomponent.tsx
    |
    |------api 
    |       |---Api.tsx
    |       
    |------screens
    |       |---App.tsx
    |
    |------Types
    |       |--Types.tsx
~~~


# コンポーネント

コンポーネント部分を作っていきましょう。
`AxiosRequestConfig`は`interface`を見ると分かります。必要に応じて追加しましょう。

~~~typescript:Apicomponent.tsx

import axios, { AxiosResponse, AxiosError, AxiosRequestConfig, AxiosInstance, Method } from 'axios'

const API_BASE_URL = 'http://localhost:xxxx' //繋ぎたいサーバに変更する

export const ApiComponent = async <T, R = AxiosResponse<T>>(
    method: Method, // 必須項目。get,delete,post,putなど利用可能
    url: string, //必須項目
    data?: any,  //BODY部分。送りたいデータを引数で渡す  
    headers?: Map<string, string>, //必要に応じて追加する
    ...requestConfig: any
): Promise<R> => {

    const config: AxiosRequestConfig = {
        ...requestConfig,
        baseURL: API_BASE_URL,
        url: url,
        method: method,
        data,
        timeout: 5000,
        headers: {
            'Content-Type': 'application/json',
            Authorization: '',
            ...headers,
        },
    }

    const axiosInstance: AxiosInstance = axios.create()

    axiosInstance.interceptors.response.use(
        (res: AxiosResponse<T>) => res,

        //AxiosErrorはここで処理を行う
        async (error: AxiosError) => {
            console.error('error:', error)
            
            if (error.response?.data) {
                return error.response?.data
            }
            
            throw Error('AxiosError');
        }
    )

    //ステータスコードによって処理を分岐可能にする
    switch ((await axiosInstance.request(config)).status) {
        //成功時はデータを返す
        case 200: return (await axiosInstance.request(config)).data

        //失敗時はエラーを出してあげる
        default:
            if (await axiosInstance.request(config) != null) {
                const err: { message: string } | null | undefined = await axiosInstance.request(config)
                if (err?.message) {
                    throw Error(err.message)
                }
            }
            throw Error('UNHANDLED_ERROR');
    }
}

~~~
## Componentのコード解説 

少しだけコード解説します。

今回利用しているのは`interceptor`です。
説明は割愛しますがAxiosの使い方は複数あります。
下記を参考にして頂くと分かりやすいと思います。

https://fabeee.co.jp/column/employee-blog/michan-01-2/

APIの成功時は、`axiosInstance.request(config)).data`に必要なデータが格納されるようにカスタマイズしました。


## AxiosError
AxiosErrorは何故必要なのか？

例えば、サーバに繋がらない時に`AxiosError`が起きます。`AxiosError`を指定すると通常では分からないエラーも確認出来るようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3028739/9f300af6-6852-8dff-caee-ab863d1efd43.png)


AxiosErrorについてQiitaに記事投稿されていました。参考にしてみてください。

https://qiita.com/yuta-katayama-23/items/5b8bf72236eec9cadf41

## コンポーネント参考記事
参考にした上記コードは下記記事になります。少しだけ改良させていただきました。

https://blog.magcho.com/2021/6/axios/

https://qiita.com/IzumiSy/items/031df1df4f4541e536b4

# APIを一元管理したい 

次にAPIのpathを一元管理出来るようにやってみます。
APIのコンポーネントの引数は第一引数にmethodと第二引数のurlは必須です。

~~~typescript:Api.tsx

import { ApiComponent } from '../component/ApiComponent';
import { LoginData } from '../types/Types';

//第一引数と第二引数は必須 
export function Api() {
    return ApiComponent("get", "/")
}

//受け渡したいデータの型をコンポーネントの引数に渡すことも可能
export function LoginApi(loginData: LoginData) {
    return ApiComponent("post", "/login", loginData)
}

//ユーザAPI　URLを繋げて利用もOK
export function UserApi(userID: string, loginData: LoginData) {
    return ApiComponent("post", "/user/" + userID ,loginData)
}


~~~

上記のやり方をすることで、APIの仕様が変わってもURLを変更するだけで済んだり、型の管理が分かりやすくなったと思います。


# interface

interfaceの一例です。

~~~typescript:Api.tsx

//型の例
export interface LoginData {
    userId: string
    passWord: string
}

~~~

# メインの処理コード
メインの処理コードです。必要に応じて変更して下さい。 

~~~typescript:App.tsx

import React, { useEffect, useState } from 'react';
import { LoginApi } from '../api/Api';
import { LoginData } from '../types/Types';

const App = () => {

    const [loginData, setLoginData] = useState<LoginData>(
        {
            userId: "",
            passWord: ""
        }
    );


    const button = () => {

        //API実行処理部分
        //rejectは必要に応じて使ってください
        new Promise((resolve, reject) => {
            resolve(LoginApi(loginData)); //API実行
        })
            .then((res) => {
                //成功処理を書く
                console.log("resolve:", res)
            })
            .catch((res) => {
                //失敗処理を書く
                console.error("reject:", res)

                //AxiosErrorが表示出来る。
                if (res == "Error: AxiosError") {
                    if (res.data != null) {
                        console.error("AxiosError:", res.data)
                    } else {
                        console.error("AxiosError:", res)
                    }
                    return
                }

                console.error("Error:", res)

            });

    };

};

//---その他処理------//

~~~

APIの実行は`resolve`に引数を指定してthenとcatchで処理を分岐させてみました。
他にも方法はありますので、メインの処理は必要に応じて変えてみて下さい。

Promise.resolve()については下記ページを参考にしてみてください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve#promise_%E4%BB%A5%E5%A4%96%E3%81%AE%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%E3%83%BC%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B_resolve_%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97

# 最後に
Axiosについて少し詳しくなりました。エラーを一つ出すのもやり方があり、勉強になりました。
また、Axiosはキャンセル処理も出来たりします。コンポーネント部分はまだまだ改良出来そうですね。