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