自動產出前端 API 型態與 TanStack Query 方法實例,加速前端開發
前後端彼此之間溝通,最麻煩的地方大概就是 API 格式、有沒有寫文件,文件有沒有定期更新維護之類的,後端可以妥善運用 Swagger 產出文件,前端只要依照 Swagger 文件就可以看到所有參數與回傳型態。
既然後端可以透過 Swagger 自動產文件,前端想必也一定可以透過某些方式拿到 Swagger 的文件格式,然後自動產出所有型態啦~ wwwww
有很多相關套件可以抓到型態,但我這次想介紹使用的是 Orval ,可以支援 OpenAPI v3 或是 Swagger v2 版本的 yaml
或是 json
格式,可以自動產出常使用到的 axios 或是 TanStack Query (React Query),不需要再看著文件一個一個寫 API。
作者本身就是因為經常使用到 Swagger editor 跟 Swagger codegen ,但覺得不夠好用所以才自己開發這個套件,因為作者自己會用到表示這個套件應該會一直更新下去(?)
在開發文件上也提供了詳細的官方文檔與 Examples。
首先身為前端的你,必須要拿到 swagger.json
或是 yaml
檔案,網址也可以,我們會使用這兩個任一檔案透過 Orval 產出需要的東西。
1. 安裝
$ npm i orval -D
or
$ yarn add orval -D
2. 在 package.json 寫入腳本
"scripts": {
// ... others scripts
"orval": "orval"
},
3. 在專案的根目錄新增一個 orval.config.ts
的檔案
拿出準備好的 swagger.json
我們要開始自動產生 API client 囉!
orval.config.ts
為 Orval 的設定檔,可以客製化 axios 與使用 TanStack Query,設定檔內容可參考官方文件 https://orval.dev 。
// orval.config.ts
import { defineConfig } from 'orval'
export default defineConfig({
api: {
output: {
mode: 'split',
target: 'src/apis/endpoints.ts',
// schemas: 'src/apis/models', // 開啟此選項 endpoints.schemas.ts 會拆成多個檔案。
client: 'react-query',
prettier: true,
override: {
// 設定自定義的 API client 檔案位置
mutator: {
path: 'src/apis/custom-client.ts',
name: 'useCustomInstance',
},
// 設定 react-query 的參數
query: {
useQuery: true,
useInfinite: true,
options: {
staleTime: 10000,
},
},
},
},
// 設定 swagger.json 的位置 (必填)
input: {
target: 'http://你的網址/swagger.json', // 也可以直接使用檔案位置
},
},
})
4. 設定客製化的 axios 實例
// custom-client.ts
import Axios, { AxiosRequestConfig } from 'axios'
import getToken from '@/utils/getToken'
export const AXIOS_INSTANCE = Axios.create({ baseURL: `${import.meta.env.VITE_API_URL}` }) // vite 拿 env 的用法
export const useCustomInstance = <T>(): ((config: AxiosRequestConfig) => Promise<T>) => {
return (config: AxiosRequestConfig) => {
const source = Axios.CancelToken.source()
const token = (Axios.AxiosHeaders = getToken())
const promise = AXIOS_INSTANCE({
...config,
cancelToken: source.token,
headers: { ...config.headers, Authorization: `${token}` },
}).then(({ data }) => {
// 這裡可以自定義回傳格式
return data.data
})
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
promise.cancel = () => {
source.cancel('Query was cancelled by React Query')
}
return promise
}
}
export default useCustomInstance
// 定義你的 ErrorType 與 BodyType
export type ErrorType<ErrorData> = ErrorData
export type BodyType<BodyData> = BodyData & {
headers?: any
data?: { token?: string; results?: any; data?: any; userInfo?: any }
code?: number
message?: string
}
5. 產生檔案
最後在終端機輸入 yarn orval
就會自動產生 endpoints.ts
與 endpoints.schemas.ts
兩個檔案,不要手動修改這兩個檔案,每次都透過 yarn orval
指令來更新這兩個檔案。
endpoints.ts
自動產生 API client 與 react-query 版本,名稱也會幫你取好,註解也會一起寫進來,真心方便(=´∀ `)人(´∀ `=)。
// endpoints.ts
/**
* Generated by orval v6.17.0 🍺
* Do not edit manually.
* Swagger Petstore
* OpenAPI spec version: 1.0.0
*/
import { useQuery, useMutation } from 'react-query'
import type {
UseQueryOptions,
UseMutationOptions,
QueryFunction,
MutationFunction,
UseQueryResult,
QueryKey,
} from 'react-query'
import type { Pets, Error, ListPetsParams, Pet, CreatePetsBody } from '../model'
import { useCustomClient } from '../mutator/custom-client'
import type { ErrorType, BodyType } from '../mutator/custom-client'
type AwaitedInput<T> = PromiseLike<T> | T
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never
/**
* @summary List all pets
*/
export const useListPetsHook = () => {
const listPets = useCustomClient<Pets>()
return (params?: ListPetsParams, version = 1, signal?: AbortSignal) => {
return listPets({
url: `/v${version}/pets`,
method: 'get',
params,
signal,
})
}
}
export const getListPetsQueryKey = (params?: ListPetsParams, version = 1) => {
return [`/v${version}/pets`, ...(params ? [params] : [])] as const
}
export const useListPetsQueryOptions = <
TData = Awaited<ReturnType<ReturnType<typeof useListPetsHook>>>,
TError = ErrorType<Error>
>(
params?: ListPetsParams,
version = 1,
options?: {
query?: UseQueryOptions<Awaited<ReturnType<ReturnType<typeof useListPetsHook>>>, TError, TData>
}
): UseQueryOptions<Awaited<ReturnType<ReturnType<typeof useListPetsHook>>>, TError, TData> & { queryKey: QueryKey } => {
const { query: queryOptions } = options ?? {}
const queryKey = queryOptions?.queryKey ?? getListPetsQueryKey(params, version)
const listPets = useListPetsHook()
const queryFn: QueryFunction<Awaited<ReturnType<ReturnType<typeof useListPetsHook>>>> = ({ signal }) =>
listPets(params, version, signal)
return { queryKey, queryFn, enabled: !!version, ...queryOptions }
}
export type ListPetsQueryResult = NonNullable<Awaited<ReturnType<ReturnType<typeof useListPetsHook>>>>
export type ListPetsQueryError = ErrorType<Error>
// ...以下省略
endpoints.schemas.ts
這個檔案會自動產生 API 的 TypeScript,直接使用該檔案內 export 的內容即可。 也可以加入 msw 或是 faker 使用唷,因為我這邊沒用到所以方法請參考官方說明文件。
使用方式
因為 Orval 只安裝在開發環境上,所以他只是輔助產出檔案,使用方式就跟以前寫 axios 或是 TanStack Query 都一樣。
// App.tsx
import React, { useEffect } from 'react'
import { useListPets } from './apis/endpoints.ts'
import { useAuthDispatch } from './auth.context'
import './App.css'
function App() {
const dispatch = useAuthDispatch()
const { data: pets, refetch } = useListPets() // 直接使用
useEffect(() => {
dispatch('token')
setTimeout(() => {
refetch()
}, 2000)
}, [refetch, dispatch])
return (
<div className="App">
<header className="App-header">
{pets?.map((pet: any) => (
<p key={pet.id}>{pet.name}</p>
))}
</header>
</div>
)
}
以後只要 API 文件有更新,只要再跑一遍 yarn orval
或是直接寫入你的執行的 dev 腳本內,每次開發時就會自己抓最新 API 文件產出格式了,是不是真的好方便~~(撒花
喜歡這篇文章就讓我們一起加入前端懶人行列吧~~(\ *≧ω≦)