Home > Back-end >  Typescript function overload depending on interface generic
Typescript function overload depending on interface generic

Time:12-27

Is there a way to declare a method overload with an optional parameter if a class generic is of a certain type? In the below example the goal is to have a class whose run method requires an argument only if the class's generic T is a number. What I've written doesn't work: I think the T in the run declaration is interpreted as a different generic than Test's generic T. Is there a way to accomplish this?


class Test<T extends string | number> {
  run<T extends number>(a: number): void
  run<T extends string>(a?: number): void
  run(a: number): void {
    console.log(a)
  }
}

const a = new Test<number>();
a.run() // This should be an error, as a's generic is a number and thus its run method should require an argument.

const b = new Test<string>();
b.run() // OK, since b's generic is a string.

CodePudding user response:

I've been trying to find a way to accomplish this using only one classes, but i'm not sure it's possible. It makes sense, since having the same function called in a different way is not very clean.

What you could do, is use an interface, a parent class and multiple child class. This would allow you to use one or the other, and stay DRY at the same time. Here is my take on this problem.

// we create an interface with an optional parameter "a"
interface ITest {
  run(a?: number) : void;
}

// we create a super class that will handle the process of the "run" function.
// this class has a function with a necessary parameter
abstract class AbstractTest<T extends string|number> {
  run(a: T) {
    console.log('ran with param: ', a);
  }
}

// We create two sub class, one for the run with no parameter, one with the run with one parameter. 
// Both classes implements the interface and calls the parent function "run".

// this class does not extends the "run" function, since it is the same as the parent one
class TestNumber<T extends number> extends AbstractTest<T> implements ITest {}

// this class extends the "run" function, since it needs to process the fact that there is no parameters passsed
// to the "run" function. I've used a default value here, but it will depend on your use case.
class TestString<T extends string> extends AbstractTest<T> implements ITest {
  public static readonly DEFAULT_VALUE = 'some value';

  run() : void {
    super.run(TestString.DEFAULT_VALUE as T)
  }
}

// we create new objects based on the need.
const a = new TestNumber<number>();
const b = new TestString<string>();

// this function call works since we've passed a parameter
a.run(42);
// this one does not work and an error is thrown.
a.run()

// this function call works great without an argument.
b.run()

I've placed comments to explain the changes. here is a typescript playground with the working code

CodePudding user response:

A conditionally-typed rest parameter is a useful choice for your method in this case:

TS Playground

class Test <T extends string | number> {
  run (...args: T extends number ? [a: number] : [a?: number]): void {
    const [a] = args;
    console.log(a); // number | undefined
  }
}

const a = new Test<number>();
a.run(42); // ok
a.run(); /*
  ^^^^^
Expected 1 arguments, but got 0.(2554) */

const b = new Test<string>();
b.run(42); // ok
b.run(); // ok
  • Related