typescriptoverloadingtypescript-classgeneric-type-parameters

Type for Optional Method Argument of Generic Class


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

Solution

  • 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