Hvordan korrekt skrive returverdier i barneklasse (typescript)


Jeg vil lage en baseklasse for å jobbe med API (la oss kalle den BaseAPI). Deretter, for hvert API, lage en wrapper som UsersAPI, LoginAPI og lignende. Så baseklassen innkapsler bare logikken for å gjøre HTTP-forespørsler og tilpasse responser til samme struktur.

Minimalt eksempel:

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 er at BaseApi.get faktisk returnerer en Promise<any>, fordi jeg ikke vet hvilke data som kommer fra backend, så det er any.

Men jeg vet hvordan serveren svarer på /login API-kall, så jeg vil motta et typet objekt (LoginResp) etter å ha ventet på AuthAPI.login.

Min linter er satt opp på en måte som ikke tillater bruk av any, og jeg vil ikke undertrykke det. Derfor prøvde jeg å følge TS-konvensjoner og erstatte any med Record<string, unknown>.

Nå får jeg en feil: Property 'username' mangler i typen 'Promise' men er påkrevd i typen 'LoginResp'.(2741)

Hvordan sette opp skriving på riktig måte?

Svar #1

Siden resultatet av json råparsing alltid vil være any (typen kan ikke bestemmes ved kompileringstid), har du 2 valg.

1. Du stoler på det som kommer tilbake fra serveren.

I dette tilfellet kan du kaste de analyserte json-dataene, du kan endre din BaseApi-type til noe slikt:

class BaseAPI {
    async get<T extends BaseResp>(url: string): Promise<T> {
        const resp = await fetch(url)
        return await resp.json() as T
    }
}

2. Du stoler ikke på det som kommer tilbake fra serveren

I dette tilfellet kunne du sette opp typevakter:

function isLoginResp(input: any): input is LoginResp {
  return typeof input === 'object' && typeof input.username === 'string'
}

Og mens du beholder resten av koden din som den er, kan du endre AuthApi-klassen din:

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)}`)
    }
}