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