Hur man korrekt anger returvärden i en barnklass (typescript)
Jag vill skapa en basklass för att arbeta med API:er (låter oss kalla den BaseAPI). Sedan, för varje API, skapa ett wrapper som UsersAPI, LoginAPI och liknande. Så basklassen kapslar endast in logik för att göra HTTP-förfrågningar och anpassa svar till samma struktur.
Minimalt exempel:
type BaseResp = Record<string, unknown>
class BaseAPI {
async get(url: string): Promise<BaseResp> {
const resp = await fetch(url)
return await resp.json()
}
}
interface LoginResp extends BaseResp {
username: string
}
class AuthAPI extends BaseAPI {
async login(): Promise<LoginResp> {
return await this.get("/login")
}
}
Problemet är att BaseApi.get faktiskt returnerar en Promise<any>, eftersom jag inte vet vilken data som kommer från backend, så det är any.
Men jag vet hur servern svarar på /login-API-anrop, så jag vill ta emot ett typat objekt (LoginResp) efter att ha avvaktat AuthAPI.login.
Min linter är inställd på ett sätt som inte tillåter användning av any, och jag vill inte undertrycka det. Därför försökte jag följa TS-konventioner och ersätta any med Record<string, unknown>.
Nu får jag ett fel: Property 'username' saknas i typen 'Promise
Hur ställer man in skrivande korrekt?
Svar #1
Eftersom resultatet av json-raw-parsing alltid kommer att vara any (typen kan inte bestämmas vid kompileringstid) har du 2 val.
1. Du litar på det som kommer tillbaka från servern.
I det här fallet kan du omvandla de analyserade json-data, du kan ändra din BaseApi-typ till något liknande detta:
class BaseAPI {
async get<T extends BaseResp>(url: string): Promise<T> {
const resp = await fetch(url)
return await resp.json() as T
}
}
2. Du litar inte på det som kommer tillbaka från servern
I detta fall skulle du kunna ställa in typvakter:
function isLoginResp(input: any): input is LoginResp {
return typeof input === 'object' && typeof input.username === 'string'
}
Och medan du behåller resten av din kod som den är, kan du ändra din AuthApi-klass:
class AuthAPI extends BaseAPI {
async login(): Promise<LoginResp> {
const response = await this.get("/login")
if (isLoginResp(response)) {
return response
}
throw new TypeError(`Expected LoginResp, got ${JSON.stringify(response)}`)
}
}
