简体   繁体   中英

TypeScript: Function overload based on class type of parameter

Is it possible to apply overloads of a TypeScript function based on the class type of the parameter?

I realize that the "classes" will be compiled down to plain functions in the output, but am hoping to get the type checking to work depending on the input type.

The goal is to create a send() function for server communication that returns a typed response based on the type of request. However, TypeScript appears to be always selecting the first overload regardless of the class type passed in.

interface IRequest {
  getBody(): object;
}

class UserRequest implements IRequest {
  getBody() {
    return { type: 'user', id: 7 };
  }
}

class CompanyRequest implements IRequest {
  getBody() {
    return { type: 'company', id: 42 };
  }
}

interface IUserResponse {
  userName: string;
}

interface ICompanyResponse {
  companyName: string;
}

function send(request: UserRequest): Promise<IUserResponse>;
function send(request: CompanyRequest): Promise<ICompanyResponse>;
async function send(request: IRequest): Promise<any> {
  const response = await fetch('https://example.com/service', {
    body: request.getBody(),
  });
  return response.json();
}

async function test() {

  // Works
  const userResponse = await send(new UserRequest());
  userResponse.userName;

  // Error: [ts] Property 'companyName' does not exist on type 'IUserResponse'. [2339]
  const companyResponse = await send(new CompanyRequest());
  companyResponse.companyName;

}

Swapping the order of the declared overloads reverses the problem (the return type will always be CompanyRequest instead of UserRequest ).

Your problem seems to be that the compiler thought that both UserRequest and CompanyRequest are structurally compatible, meaning they are essentially the same type. So the first overload is always chosen, and bad things happen. The easiest way to avoid this is to add a properties to at least one of the types so that the compiler recognizes they are distinct types . Here's one possible way:

class UserRequest implements IRequest {
  getBody() {
    return { type: 'user', id: 7 };
  }
  readonly className = "UserRequest"
}

class CompanyRequest implements IRequest {
  getBody() {
    return { type: 'company', id: 42 };
  }
  readonly className = "CompanyRequest"
}

In that case the readonly property className gets some literal string values that differ. Hope that helps. Good luck!

What about a generic approach?

async function send<T>(request: T): Promise<T> {
    const response = await fetch('https://example.com/service', {
        body: request.getBody(),
    });
    return response.json();
}

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