📝
NuxtのuseFetchのカスタマイズが思いのほか面倒だった
useFetchのbaseURLオプションを毎度書くのが非常に面倒なので、ラッパーコンポーザブルをサクッと書こうとしたら意外難しかったのでメモ。
まずnuxt.config.tsにbaseURLオプションを指定しておく。
nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public:{
baseURL: "http://example.com/",
}
}
}
ラッパーコンポーザブルuseApi.tsを試しに👇のように書くと、useFetchにオプションを渡すときに、型の不一致を起こす。どうやらUseFetchOptions<T>の型引数の指定が甘いらしい。
composables/useApi.ts
import type { UseFetchOptions } from "#app"
import type{FetchError} from "ofetch"
type Req= string|Ref<string>|(()=>string)
function useApi<T, ErrorT=FetchError>(
url: Req, opts?: UseFetchOptions<T> //<--型引数の指定が甘い
){
const {public: {baseURL}}= useRuntimeConfig()
const fetchOpts= { ...opts, baseURL}
return useFetch<T, ErrorT>(url, fetchOpts) //<--❌fetchOptsで型の不一致を起こす
}
export default useApi
UseFetchOptionsの型定義は、やたらと込み入っていて、しかもexportされていない型もあったりして非常に厄介、とくにmethodがらみの引数のあたりは非常に込み入っているので簡略化し、以下のように型引数を指定してみる。とりあえず型のエラーは消えた。
composables/useApi.ts
import type { UseFetchOptions } from "#app"
import type{FetchError} from "ofetch"
import type {AvailableRouterMethod} from "nitropack"
type Req= string|Ref<string>|(()=>string)
//メソッドの型は簡略化
type Method= AvailableRouterMethod<string>
type KeysOf<T> = Array<T extends T ? keyof T extends string ? keyof T : never : never>
// ラッパー関数に型引数DefaultTを追加
function useApi<T, ErrorT=FetchError, DefaultT= undefined>(
url: Req,
opts?: UseFetchOptions<
//省略せず型引数を全て指定する
T, T, KeysOf<T>, DefaultT, string, Method
>
){
const {public: {baseURL}}= useRuntimeConfig()
const fetchOpts= { ...opts, baseURL}
return useFetch<
//こちらも全て指定
T, ErrorT, string, Method, T, T, KeysOf<T>, DefaultT
>(url, fetchOpts)
}
export default useApi
しかし、これをコンポーネントで使おうとすると、defaultオプションで型エラーが起きてしまう。むむむ。
example.vue
type Book={name: string}
//❌defaultオプションで型エラーになる
const {data}= useApi<Book[]>("/hogehoge/books", {default: ()=>[])
もう一度useFetchの型定義を確認すると、なんとオーバーロードされているではないか。2つの違いは型引数DefaultTのデフォルト値だけ。要は、defaultオプションの有無によって返される型が異なるため2つのシグニチャーが必要になっている、と理解。
というわけで、useApiも、2つのシグニチャーを用意する。めっちゃ冗長だが、これだと、コンポーネント側でも型推論がうまくいく。
useApi.ts
import type { UseFetchOptions } from "#app"
import type{FetchError} from "ofetch"
import type {AvailableRouterMethod} from "nitropack"
type Req= string|Ref<string>|(()=>string)
type Method= AvailableRouterMethod<string>
type KeysOf<T> = Array<T extends T ? keyof T extends string ? keyof T : never : never>
// オプションの型はここで宣言
type Options<T, DefaultT>= UseFetchOptions<
T, T, KeysOf<T>, DefaultT, string, Method
>
// 戻り値の型を宣言
type ReturnT<T, ErrorT, DefaultT>= ReturnType<typeof useFetch<
T, ErrorT, string, Method, T, T ,KeysOf<T>, DefaultT>
>
// シグニチャー1(Default = undefined)
function useApi<T, ErrorT= FetchError, DefaultT= undefined>(
url: Req, opts?: Options<T, DefaultT>
):ReturnT<T, ErrorT, DefaultT>
// シグニチャー2(Default = T)
function useApi<T, ErrorT= FetchError, DefaultT= T>(
url: Req, opts?: Options<T, DefaultT>
):ReturnT<T, ErrorT, DefaultT>
// 関数本体
function useApi<T, ErrorT=FetchError, DefaultT= undefined>(
url: Req, opts?: Options<T, DefaultT>
){
const {public: {baseURL}}= useRuntimeConfig()
const fetchOpts= { ...opts, baseURL}
return useFetch<
T, ErrorT, string, Method, T, T, KeysOf<T>, DefaultT
>(url, fetchOpts)
}
export default useApi
それにしても、冗長なコード。何とかならないものか。
Discussion