What is the proper way to handle dynamic `.apply()` usage in TypeScript?

Viewed 314

Consider the following code, which defines an object with three methods. Each method takes a different number of arguments, and then the code at the bottom calls one of the methods (dynamically, based on the value of a variable) via apply() with an args array.

class Obj {
  a() {
    return 0; 
  }
  b(one: string) {
    return 1;
  }
  c(one: string, two: string) {
    return 2;
  }
}

const obj = new Obj();

function callMethod(method: keyof Obj, ...args: any[]): void {
  obj[method].apply(obj, args) // Errors
}

callMethod('a');

When compiling the above code, TypeScript complains, presumably because the args array could have length 0 but the b() and c() methods require some parameters:

The 'this' context of type '(() => number) | ((one: string) => number) | ((one: string, two: string) => number)' is not assignable to method's 'this' of type '(this: Obj) => number'.
  Type '(one: string) => number' is not assignable to type '(this: Obj) => number'.

This example illustrates the use case of a JavaScript problem that dynamically calls method names based on the value of some variable.

How can I signal to the type checker that:

  • any time I call method a() the length of args will be 0
  • any time I call method b() the length of args will be 1
  • any time I call method c() the length will be 2

Or, if someone knows of a better way to solve this, I'm open to suggestions!

1 Answers

You can use the Parameters<T> helper type to extract the function parameters in a generic way:

function callMethod<T extends keyof Obj>(
  method: T,
  ...args: Parameters<Obj[T]>
): void {
  (obj[method] as any).apply(obj, args);
}
Related