I TypeScript, hur skapar jag en generisk funktion som härleder parameternas typer för en funktion givet dess klassnamn och funktion?


I princip vill jag tillhandahålla en wrapper-funktion för alla RPC-anrop jag gör. Detta är så att jag kan logga specifik information om varje RPC-anrop utan att använda en middleware. Jag vill kunna få parametern för metoden som anropas genom att göra rpc[serviceName][method] med TypeScript.

Detta är min nuvarande implementering där params inte är tillräckligt specifik:

async rpcWrapper<Service extends keyof IRpc>(
    serviceName: Service,
    method: keyof IRpc[Service],
    params: Object,
  ) {
    return rpc[serviceName][method]({ ...params });
  }

Jag har också försökt göra detta men har fått ett fel:

async rpcWrapper<Service extends keyof IRpc, Method extends keyof IRpc[Service]>(
    serviceName: Service,
    method: Method,
    params: Parameters<Method>, // Type 'Method' does not satisfy the constraint '(...args: any) => any'.
  ) {
    return rpc[serviceName][method]({ ...params });
  }

IRPC-gränssnitt

    interface IRpc {
        ExampleService: ExampleService;
        ExampleService2: ExampleService2;
        ExampleService3: ExampleService3;
    }

typ av en ExampleService

export declare class ExampleService {
  public Login(req: LoginReq): Promise<LoginResp>;
  public Login(ctx: ClientInvokeOptions, req: LoginReq): Promise<LoginResp>;

  public Logout(req: LogoutReq): Promise<CommonResp>;
  public Logout(ctx: ClientInvokeOptions, req: LogoutReq): Promise<CommonResp>;
}

export interface LoginReq {
  username: string;
  email: string;
}

Vad jag vill

rpcWrapper("ExampleService", "Login", {  }) 
// Autocomplete tells me that I can fill in username and email

Svar #1

Den korrekta typinferensen är ibland svår att uppnå. I din funktion är Method-typen bara nyckeln till en metod i en tjänst och du kan inte komma åt parametrarna för en PropertyKey. T.ex. Parameters<"Login">

async rpcWrapper<Service extends keyof IRpc, Method extends keyof IRpc[Service]>(
    serviceName: Service,
    method: Method,
    // Method is just the method key of your function
    params: Parameters<Method>, // Type 'Method' does not satisfy the constraint '(...args: any) => any'.
  ) {
    return rpc[serviceName][method]({ ...params });
  }

För att få rätt metod för din tjänst måste du komma åt denna metod så här IRPc[Service][Method] //<- men detta kommer att resultera i okänt Därför måste du på något sätt kontrollera att din IRPc[Service][Method] är en giltig funktion. Du kan göra det genom att skriva en verktygstyp som kontrollerar om den tillhandahållna generiska typen utvidgar någon funktion type CastFn<T>=T extends AnyFn ? T : never Nu kan du komma åt parametrarna för din metod så här Parameters<CastFn<IRPc[Service][Method]>>.

interface IRpc {
  ExampleService: ExampleService;
  ExampleService2: ExampleService2;
}



interface LoginResp {
  message: string
}

export declare class ExampleService {
  public Login(input: { req: LoginReq, ctx?: ClientInvokeOptions }): Promise<LoginResp>;
  public Logout(input: { req: LogoutReq, ctx?: ClientInvokeOptions }): Promise<CommonResp>;
}


export declare class ExampleService2 {
  public Study(req: StudyReq): Promise<LoginResp>;
}

export interface StudyReq {
  canStudy: boolean;
}

export interface LoginReq {
  username: string;
  email: string;
}

interface LogoutReq {
  username: string;
}

interface CommonResp { }

interface ClientInvokeOptions { }

declare const rpc: IRpc;
type AnyFn = (...args: any[]) => any

type CastFn<T> = T extends AnyFn ? T : never
function rpcWrapper<
  ServiceKey extends keyof IRpc,
  MethodKey extends keyof IRpc[ServiceKey],
  ClassMethod extends AnyFn = CastFn<IRpc[ServiceKey][MethodKey]>
>(
  serviceName: ServiceKey,
  method: MethodKey,
  ...params: Parameters<ClassMethod>
) {
  
  return (rpc[serviceName][method] as ClassMethod)(...params); 
}
type X = Parameters<IRpc["ExampleService"]["Login"]>
// Can put anything
rpcWrapper("ExampleService", "Login", { a: "a" }) // invalid

// I should be restricted to this
rpcWrapper("ExampleService", "Login", { req: { username: "haha", email: "[email " } })  //valid```