客戶端應(yīng)用程序的開發(fā)人員每天與API打交道。根據(jù)操作的成功與否或業(yè)務(wù)邏輯,標(biāo)準(zhǔn)化API響應(yīng)是一個好習(xí)慣。通常,響應(yīng)包括狀態(tài)、錯誤等標(biāo)準(zhǔn)字段。

有了這些標(biāo)準(zhǔn)字段,開發(fā)人員可以對操作的狀態(tài)做出反應(yīng),并構(gòu)建與應(yīng)用程序的進(jìn)一步用戶交互。如果注冊成功,應(yīng)該關(guān)閉表單并顯示成功消息。然而,如果數(shù)據(jù)格式不正確,驗(yàn)證錯誤應(yīng)該在表單中顯示。

這就提出了如何在項(xiàng)目中方便、快速和靈活地描述響應(yīng)類型的問題。

遇到的問題

有時,項(xiàng)目中的響應(yīng)類型僅用一種類型描述,帶有多個可選參數(shù)。在大多數(shù)情況下,這可能足夠了,TypeScript在編寫代碼時會建議這些參數(shù),但需要額外檢查這些參數(shù)的存在。下面是一個這樣的類型的示例:

export enum ApiStatus {
OK = ok, ERROR = error, FORM_ERRORS = form_errors, REDIRECT = redirect, } export type ApiData = { status: ApiStatus error?: string errors?: Record<string, string> url?: string }

這種方法的唯一優(yōu)點(diǎn)是它的簡單性。我們可以將ApiData類型添加到任何響應(yīng)類型中,這樣就足夠了。

export type UserProfile = {
id: number
name: string
last_name: string
birthday: string
city: string
}

export type UserProfileResponse = ApiData & {
user: UserProfile
}

// to simulate an API call
const updateProfileAPI = async(data: Partial<UserProfile>): Promise<UserProfileResponse> => {
return Promise.resolve({} as UserProfileResponse)
}

然而,我認(rèn)為這種單一的優(yōu)勢被一個顯著的劣勢所抵消。這種方法的缺點(diǎn)是缺乏透明度。

此外,通過向響應(yīng)類型添加這樣的類型,你永遠(yuǎn)無法確切知道特定請求的響應(yīng)將是什么。想象一下,對于一個POST請求,你可以從API獲得有限數(shù)量的響應(yīng)場景。

場景可能是以下之一:

結(jié)果表明,我們不能僅僅通過查看響應(yīng)類型就了解我們確切的響應(yīng)選項(xiàng)。要了解所有可能的響應(yīng)變體,你需要打開執(zhí)行請求和處理響應(yīng)的函數(shù)的代碼。

響應(yīng)類型的實(shí)用類型

上述缺點(diǎn)可以通過自定義實(shí)用類型來解決。每個場景都有單獨(dú)的類型:成功操作、服務(wù)器錯誤、驗(yàn)證錯誤或強(qiáng)制重定向。

這些類型可以單獨(dú)使用或組合使用,以反映特定響應(yīng)的所有可能響應(yīng)選項(xiàng)。每種類型都有一個通用類型,允許傳遞對應(yīng)于該響應(yīng)的數(shù)據(jù)類型的數(shù)據(jù)。

export enum ApiStatus {
OK = ok, ERROR = error, FORM_ERRORS = form_errors, REDIRECT = redirect, } export type ApiSuccess<T extends Record<string, unknown> | unknown = unknown> = T & { status: ApiStatus.OK, } export type ApiError<T extends Record<string, unknown> = { error: string } > = T & { status: ApiStatus.ERROR, } export type ApiFormErrors<T extends Record<string, unknown> = { errors: Record<string, string> }> = T & { status: ApiStatus.FORM_ERRORS, } export type ApiRedirect<T extends Record<string, unknown> = { url: string }> = T & { status: ApiStatus.REDIRECT, } export type ApiResponse<T extends Record<string, unknown> | unknown = unknown, K extends Record<string, unknown> = { error: string }, R extends Record<string, unknown> = { errors: Record<string, string> }> = ApiSuccess<T> | ApiError<K> | ApiFormErrors<R>

此外,我還創(chuàng)建了一個通用的ApiResponse類型,它包括幾個實(shí)用類型。這將節(jié)省為每個POST請求添加所有場景的時間。

以下是針對不同場景使用這些實(shí)用類型的示例:

export type FetchUserProfile = ApiSuccess<{
user: UserProfile
}>

export type FetchUserConfig = ApiSuccess<{
config: Record<string, string | number | boolean>
}> | ApiError

export type AddUserSocialNetworkAsLoginMethod = ApiResponse<{
social_network: string,
is_active: boolean
}, { message: string }> | ApiRedirect<{ redirect_url: string }>

實(shí)際差異

下面是一個用戶個人資料的類型示例,以及用戶個人資料更新功能返回的響應(yīng)類型。

const updateProfile = async(): Promise<void> => {
try {
const data = await updateProfileAPI({ name: 'New name' })

// [!!!] Typescript does not highlight that the 'user' property could not exist on the 'data' property
// In the case when data.status === ApiStatus.ERROR|FORM_ERRORS|REDIRECT
console.log(data.user.id)

if (data.status === ApiStatus.OK) {
updatedProfileState(data.user)
return
}

if (data.status === ApiStatus.ERROR) {
// Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
// Type 'undefined' is not assignable to type 'string'.
showNotification('danger', data.error)
return
}

if (data.status === ApiStatus.FORM_ERRORS) {
// Argument of type 'Record<string, string> | undefined' is not assignable to parameter of type 'Record<string, string>'.
// Type 'undefined' is not assignable to type 'Record<string, string>'.
showValidationErrors(data.errors)
return
}

if (data.status === ApiStatus.REDIRECT) {
// Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
// Type 'undefined' is not assignable to type 'string'.
redirect(data.url)
return
}

throw new Error('Something went wrong...')
} catch (err) {
console.error('User: updateProfile - ', err)
}
}

這是一張TypeScript如何檢查這段代碼的圖片:

在圖片中,你可以看到TypeScript突出顯示了一些標(biāo)準(zhǔn)響應(yīng)的預(yù)期值,如error、errors或url。這是因?yàn)閘inter(代碼檢查器)認(rèn)為這些值可能是未定義的。這個問題可以通過在狀態(tài)檢查時增加額外的檢查來輕松解決,但它已經(jīng)展示了這種方法的問題。

另外,請注意,在console.log(data.user.id)這一行中,user值沒有被突出顯示為可能未定義。如果我們收到的響應(yīng)類型不是成功的,這是我們會遇到的情況。

使用ApiResponse等實(shí)用類型,我們就不會遇到這樣的問題。

export type UserProfileResponseV2 = ApiResponse<{
user: UserProfile
}> | ApiRedirect

const newUpdateProfileAPI = async(data: Partial<UserProfile>): Promise<UserProfileResponseV2> => {
return Promise.resolve({} as UserProfileResponseV2)
}

這是一張展示TypeScript如何對這段代碼進(jìn)行l(wèi)int檢查的圖片:

在這種情況下,一切工作正常進(jìn)行:

TypeScript理解對于相應(yīng)的狀態(tài),將會有相應(yīng)的標(biāo)準(zhǔn)字段。
它指出,在除了成功的響應(yīng)之外的所有響應(yīng)類型中,user值可能是未定義的。然而,在檢查響應(yīng)的成功狀態(tài)后,這個值不再被突出顯示,并且是已定義的。

結(jié)論

在項(xiàng)目中實(shí)現(xiàn)這些實(shí)用類型后,開發(fā)人員體驗(yàn)顯著提升?,F(xiàn)在,類型完全對應(yīng)于API可以提供的可能響應(yīng)場景。

這也有助于避免在某些響應(yīng)類型中不可用值的使用潛在錯誤,就像user值的例子一樣。

此外,不需要查看代碼中的響應(yīng)處理實(shí)現(xiàn)就能理解實(shí)際的響應(yīng)類型。你可以立即看到完整的情況。

如果你對這些實(shí)用類型是如何工作的感興趣,你可以查看TypeScript Playground頁面。

如何找到更多同類API?

冪簡集成是國內(nèi)領(lǐng)先的API集成管理平臺,專注于為開發(fā)者提供全面、高效、易用的API集成解決方案。冪簡API平臺可以通過以下兩種方式找到所需API:通過關(guān)鍵詞搜索API、或者從API Hub分類頁進(jìn)入尋找。

原文鏈接:https://itnext.io/how-to-write-api-response-types-with-typescript-f8152ddd43dd

上一篇:

使用 Python 和 Flask 開發(fā) RESTful API

下一篇:

交叉熵的Numpy實(shí)現(xiàn):從理論到實(shí)踐
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊

多API并行試用

數(shù)據(jù)驅(qū)動選型,提升決策效率

查看全部API→
??

熱門場景實(shí)測,選對API

#AI文本生成大模型API

對比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個渠道
一鍵對比試用API 限時免費(fèi)

#AI深度推理大模型API

對比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費(fèi)