简体   繁体   中英

Extracting the parameters of a function nested in an interface in Typescript

Is it possible to construct a function with a different return type in the following example?

interface APIResponse<T> {
  code: T;
}

interface Registry {
  a: {
    get: (dc: string, id: string) => string;
  };
  b: {
    get: (id: string) => string;
    list: () => string[];
  };
}

function callService<
  T extends keyof Registry,
  R extends keyof Registry[T],
  F extends Registry[T][R]
>(
  svc: T,
  method: R,
  callback: (
    ...args: Parameters<F>        // Type 'Registry[T][string] | Registry[T][number] | Registry[T][symbol]' is not assignable to type '(...args: any) => any'.
  ) => APIResponse<ReturnType<F>> // Type 'Registry[T][string] | Registry[T][number] | Registry[T][symbol]' is not assignable to type '(...args: any) => any'.
) {}

// although the above has a type error, the arguments and return type here are correctly inferred by vscode:
callService("a", "get", (dc, id) => ({ code: dc + id }));

If I hardcode the service and method name the compiler is happy:

function callService2<
  T extends keyof Registry,
  R extends keyof Registry[T],
>(
  svc: T,
  method: R,
  callback: (
    ...args: Parameters<Registry["a"]["get"]>  // this is fine
  ) => APIResponse<ReturnType<Registry["a"]["get"]>>  // this is fine
) {}

There is an open issue in GitHub, microsoft/TypeScript#21760 , pointing out that nested generic property lookups fail to be assignable to the expected subproperty constraints. The issue is listed as a bug, and there might have been some effort to address it, but it looks like not much is happening with it right now.

In your case, it means that the type Registry[T][R] is not seen as assignable to any function type, despite the fact that it must be a function type. I would suggest, as a workaround, to use something more forgiving than the Parameters and ReturnType utility types provided by the standard library. For example:

type Params<T> = T extends (...args: infer P) => any ? P : never;
type RetType<T> = T extends (...args: any) => infer R ? R : never;

function callService<
  T extends keyof Registry,
  R extends keyof (Registry[T]),
  >(
    svc: T,
    method: R,
    callback: (
      ...args: Params<Registry[T][R]>
    ) => APIResponse<RetType<Registry[T][R]>>
  ) { }

As an aside, note that your original code doesn't have a good inference site for F , and so it will fall back to its constraint, Registry[T][R] . You don't even want F to be anything narrower than Registry[T][R] , right? So I've removed F entirely and replaced it with Registry[T][R] .

Anyway, the difference between Params / RetType and Parameters / ReturnType is that the new versions do not try to constrain their type parameters to anything. If you evaluate Params<T> and RetType<T> on a non-function type T like string , it will result in never . Otherwise it's the same as Parameters<T> and ReturnType<T> . That relaxed restriction is enough to stop the compiler from worrying that it can't guarantee that Registry[T][R] is a function type.

Of course it will turn out that when you call callService() that, for any actual specifications of T and R , Registry[T][R] will be a function and the type of callback will be as expected:

callService("a", "get", (dc, id) => ({ code: dc + id }));
// function callService<"a", "get">(
//   svc: "a", method: "get", callback: (dc: string, id: string) => APIResponse<string>
// ): void

Playground link to code

If you enforce that the shape of Registry is predicable in later generic types:

type RegistryBase = Record<string, Record<string, ((...args: any) => any)>>

interface Registry extends RegistryBase {
    a: {
        get: (dc: string, id: string) => string;
    };
    b: {
        get: (id: string) => string;
        list: () => string[];
    };
}

All your other types fall into place.

Playground example

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