Type for Optional Method Argument of Generic Class

293 views Asked by At

I was wondering if there is a way to declare a generic class with default generic type which:

  1. By default would allow calling a method of the class without passing arguments
  2. If another generic type is defined, then the method would be able to be called only when passing an argument of the generic type.

Pseudocode

class ClassA<MyGenericType = OptionalArgumentType> {
    public methodWithGenericArgument(argumentA: MyGenericType): void {
        // Do smth
    }
}

// 
const instanceX = new ClassA();
instanceX.methodWithGenericArgument(); // CORRECT! We use default optional argument type
//
const instanceY = new ClassA<NotOptionalArgumentType>();
instanceY.methodWithGenericArgument(); // ERROR! Compiler should throw an error here, because we defined NOT OPTIONAL type
//
const argumentValue: NotOptionalArgumentType;
const instanceZ = new ClassA<NotOptionalArgumentType>();
instanceZ.methodWithGenericArgument(argumentValue); // CORRECT! We pass argument with required value
2

There are 2 answers

0
Oleg Valter is with Ukraine On BEST ANSWER

With some trickery, you can make this work. The recipe includes:

  1. never as the default type for the generic type parameter
  2. rest parameters with tuple types added in TS 3.0

The idea is to conditionally switch between an empty tuple and a tuple of one argument (or more, or several conditions - go wild depending on your implementation). Here is how this would look like:

class ClassA<MyGenericType = never> {
    public methodWithGenericArgument(...args: MyGenericType extends never ? [] : [MyGenericType]): void {
        // Do smth
    }
}

type NotOptionalArgumentType = number;

const instanceX = new ClassA();
instanceX.methodWithGenericArgument(); // OK
instanceX.methodWithGenericArgument(15); // ERROR

const instanceY = new ClassA<NotOptionalArgumentType>();
instanceY.methodWithGenericArgument(); // ERROR
instanceY.methodWithGenericArgument(15); // OK

let argumentValue!: NotOptionalArgumentType;
const instanceZ = new ClassA<NotOptionalArgumentType>();
instanceZ.methodWithGenericArgument(); // ERROR
instanceZ.methodWithGenericArgument(argumentValue); // OK

Playground

0
Lazar Ljubenović On

Almost. It cannot be a method, but a property which happens to have a function assigned to it. Depending on what you're doing, it might or might not work.

class ClassA<T = string> {
  kindaMethod: T extends string ? (() => void) : ((arg: T) => void ) = ((maybeArg?: T) => {
      // do sth
  }) as any
}

const x = new ClassA()
x.kindaMethod()
x.kindaMethod('a') // correctly an error

const y = new ClassA<number>()
y.kindaMethod() // correctly an error
y.kindaMethod(1)

Playground

You also need to cast the "method" to any, so be careful with its implementation if you're actually respecting the public API that you're exposing from the class.