TypeScriptでREST APIの型を組む時に型の恩恵を受けるように設計する
REST APIの呼び出し時に呼び出し元でtry/catchしてるコードをよく見る
code:typescript
const fetchItems = ...
try {
const res = await fetchItems()
setItems(res.items)
} catch(error) {
alert('Error')
}
この問題はせっかくfetchItemsを定義してでリクエストの詳細を隠蔽しているのにも関わらずエラーどのようなエラーがthrowされるかは詳細をみて判断しないといけない点である。
try/catchのcatchに渡るエラーオブジェクトには型を付けれないのでリクエストエラーの場合にthrowするように設計するとどうしてもそのようになってしまう。
実際にfetchItemsを実装する時にはどのエンドポイントを叩いてどのようなレスポンスが返るかを確認しながら型を定義しているはずなのでその時点でどのようなエラーレスポンスが返るかもわかるはずである。その仕様に関してもfetchItems内に隠蔽して呼び出し元が安心して使えるようにしたい。
そこで自分はfetchItemsのようにREST APIの詳細をラップした機能を作る場合に以下のような返り値を設定している。
code:typescript
export type ApiResult<Data = {}, Error = string> = Promise<
{ result: true; data: Data } | { result: false; error: Error }
これをどう使うかと言うと
code:typescript
type Item = { id: number, name: string }
const fetchItems = async (): ApiResult<Array<Item>> => {
try {
const { data } = await axios.get<Array<Item>>("/items");
return { result: true, data };
} catch (error: any) {
return { result: false, error: error.message };
}
};
このようにfetchItems内でcatchしてreturnするようにする。
こうすることで呼び出し側はtry/catchを書かなくて済むようになり、Conditinal Typesを使って型安全に分岐処理が書ける
code:typescript
const res = await fetchItems()
if(res.result) {
res.data // Array<Item>
res.error // 型エラー
} else {
res.error // string
res.data // 型エラー
}
#typescript