简体   繁体   中英

Generic TypeScript API with types as parameters

Let's say I have two functions like this (I currently have like 20 different that looks pretty much the same. Only difference is the route and the DTOs) I don't want to send both route and DTO in the function call as that would make the function calls more messy/complicated. So I'd rather only call it with either Asset | Personnel | Something else that is valid

 async getAllAssets() {
    return await this.get<AssetDto[]>('assets/assets')
  }

 async getAllPersonnels() {
    return await this.get<PersonnelDto[]>('personnel/personnels')
  }

And I want to make it more generic so I only need one function instead of two. How do I implement that? My own try is below. Maybe it will make it more clear what I actually want as well. I'm a total newbie with TypeScript and only been at it for one week. My "dream" implementation would also include enum so I could call the function with eg Entity.Asset or Entity.Personnel and then under the hood it then knows that it should use the the route and dto for either Asset or Personnel.

export type Asset = {
    route: '/assets/asset',
    dto: AssetDto[]
}

export type Personnel = {
    route: '/personnel/personnel',
    dto: PersonnelDto[]
}

export type Entity = Asset | Personnel

And here is the example of a more generic function:

 async getAll<T extends Entity>(entity: T) {
    return await this.get<typeof entity.Dto>(entity.route)
  }

But I don't know how to actually call the function with a type? Or is it even possible to do it like this?

  async howIWantAFunctionCallToBeLike() {
    await this.getAll(Entity.Asset))
  }

As Tobias has said, you need something to survive to runtime to pass to your generic function. I recommend using a simple generic class:

class Route<T> {
  constructor(readonly path: string) {
  }

  transform(data: unknown): T[] {
    // unsafe cast by default; subclasses could do something more
    return data as T[];
  }
}

const personnelRoute = new Route<PersonnelDto>('/personnel/personnel');

async function getAll<T>(route: Route<T>): Promise<T[]> {
  return route.transform(await doFetch(route.path));
}

It is possible to utilize generics here to infer the return type based on something passed into the function.

You could create an interface like this:

interface Entities {
  '/assets/asset': AssetDto[],
  '/personnel/personnel': PersonnelDto[]
} 

With this interface, we can create a generic function which returns the correct type based on the passed route.

async getGeneric<T extends keyof Entities>(route: T){
  return await this.get<Entities[T]>(route)
} 

async otherFn() {
  const a = await this.getGeneric('/assets/asset')
  //    ^? AssetDto[]
  const b = await this.getGeneric('/personnel/personnel')
  //    ^? PersonnelDto[]
}

Playground


Or use an enum:

enum Routes {
  Asset = '/assets/asset',
  Personnel= '/personnel/personnel'
}

interface Entities {
  [Routes.Asset]: AssetDto[],
  [Routes.Personnel]: PersonnelDto[]
} 
async getGeneric<T extends keyof Entities>(route: T){
  return await this.get<Entities[T]>(route)
} 

async howIWantAFunctionCallToBeLike() {
  const a = await this.getGeneric(Routes.Asset)
  //    ^? AssetDto[]
  const b = await this.getGeneric(Routes.Personnel)
  //    ^? PersonnelDto[]
  }

Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM