今まで様々なリクエストのラッピングと使用を見てきましたが、例外なく多くは無効なラッピングであったり、使用者をより複雑にしてしまうものでした。そこで、ラッピングの一般的な実践について書こうと思い、本記事が生まれました。
以下の例には Axios Fetch UseFetch (Nuxt3) が含まれます。
DX 体験#
チーム内の主導として、DX 体験(開発者体験)に合ったラッピングを行うことは非常に重要です。結局、あなたがラッピングしたものは他の人によって大量に使用され、プロジェクト全体のアプリケーションシーンを考慮する必要があります。DX 体験は最優先にすべきです。
そのため、以下のポイントを挙げました...
- 優雅な API 呼び出し
- ビジネスロジックとの切り分け
- カスタム例外処理の能力
- デフォルトの動作を阻止することを許可
- 生データを取得する能力をサポート
- 新しいインターフェースの負担を軽減
これらのポイントについて、順に分析していきます。
優雅な API 呼び出し#
まずはケースを見てみましょう。
/* 1. フォルダ分類のAPI */
import { getList } from "@/api/demo";
/* 2. 別名で全APIを統一してインポート */
import api from "api";
async function preload(){
try {
/* 1のインポート方式で呼び出し */
const result = await getList();
/* 2のインポート方式で呼び出し */
const result = await api.demo.getList();
// 応答が成功コードであればここに到達します
} catch (error){
// すべてのビジネスコードの例外とhttpStatusの例外はここに到達します。自分で例外を処理する必要がある場合
if(error instanceof Error){
// トースト....
}
}
}
上記のケースでは、2 つのインポート方式と使用例を区別しました。次に、一般的なビジネス、例えば ページネーション について詳しく掘り下げます。
const result = await list();
type C = () => Promise<boolean>
/**
resultのデータ構造はページネーションを例に
result = {
pageNum:number
pageSize:number
list:Map<number,T>
to:C // 指定ページへ
next:C // 次のページ
back:C // ホームページに戻る
status:"loading" | "end"
}
*/
このようにして、他のビジネス担当者が API に費やす時間と労力を減らし、不要な変数を定義することなく、必要なのは API を呼び出すことだけです。
次に、どのようにこのようなカスタム Hook を設計するかについて説明します。
API の流れ#
上記のロジックに基づいて、具体的な呼び出しの流れを簡単に導き出すことができます。
ページ ---> ビジネス API ---> 根リクエスト
根リクエスト --> httpStatus の処理 -> ビジネスコードの処理 ->
--> ページネーションのラッピング -> 戻す
--> ラッピングしない -> 戻す
封装を開始します。Axios を例に#
// request.ts
import axios from "axios"
import type { AxiosRequestConfig } from "axios"
/* グローバルaxiosインスタンスを作成 */
const axiosiInstance = axios.create({
baseUrl:"https://api.example.com/",
timeOut:6000
});
/* バックエンドの共通応答体 */
export interface Request<T=null>{
code: number;
data: T;
success: boolean;
msg: string | null
}
/* カスタムErrorCodeをサポート */
export interface ErrorInstall {
[key:string|number]: string;
}
const StatusCode = 200;
export default async function request<R=null>(conf:AxiosRequestConfig,err:ErrorInstall,dialog=true){
/* 通常のビジネスロジックでは、さまざまなカスタムヘッダーを持つことが常に求められます */
/* ここでconfのクローンを作成 */
const config: AxiosRequestConfig = {
// headersを上に置くことでconfigにheadersのデフォルト値を持たせることができます
// 下の...confにheadersがあればデフォルト値を上書きしてheadersを常に存在させることができます
headers:{},
...conf,
}
/* ここで共通ヘッダーを追加します。例えばトークン */
// TODO ....
const {status,data,statusText} = await axiosiInstance<Request<R>>(config);
let error:Error;
/* httpStatusの処理 */
if(status !== StatusCode) error = new Error(statusText);
/* ビジネスコードがあればhttpStatusを上書き */
if (data && data.code !== StatusCode && err[data.code]) {
error = new Error(err[data.code]);
}
/* errorが存在する場合は共通のエラーポップアップをトリガー */
if(error instanceof Error){
if(dialog) showFailToast(error.message);
throw { error, data };
}
// 応答
return data;
}
これで、根リクエストのラッピングが完了しました。
次の章では、応答内容のページネーションラッピング処理について説明します。