Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional method parameters based on Generic type

I am in the process of writing a new TypeScript class which takes a generic value that will be used as the input to a function. If a value isn't given, than the function should not take any inputs.

Ideally the class would be overloaded like this.

class Emitter<T = void> {
    public activate(): void // When T is void
    public activate(arg: T): void // When T isn't void
    public activate(arg?: T) { /* ... */ }
}

Having the method be a function property works in theory but requires a @ts-ignore on the method's implementation.

type OneArgFn<T> = T extends void
    ? () => void
    : (arg: T) => void

interface Emitter<T> {
    readonly activate: OneArgFn<T>
}

Another possibility is to export a different constructor when a Generic is provided or not, such as the following

interface EmitterNoArg extends Emitter {
    activate: () => true
}
interface EmitterOneArg<T> extends Emitter<T> {
    activate: (arg: T) => void
}

interface EmitterConstructor {
    new(): Emitter

    new(): EmitterNoArg
    new<T>(): EmitterOneArg<T>
}

but then to export it the unknown keyword is required.

export default Emitter as unknown as EmitterConstructor

These, do not seem to be optimal. Is there a proper way to have conditional arguments based on the Generic's type? I would think TypeScript's new conditional types would solve this issue.

like image 966
Mo Pro Avatar asked Jan 27 '23 11:01

Mo Pro


1 Answers

One way to do it would be using tuples in rest parameters in a separate public signature:

type OneArgFn<T> = T extends void
    ? () => void
    : (arg: T) => void

class Emitter<T = void> {
    public activate(...a: Parameters<OneArgFn<T>>): void
    public activate(arg?: T) { /* ... */ }
}

new Emitter().activate();
new Emitter<string>().activate("") // in 3.4 argument names are preserved
like image 153
Titian Cernicova-Dragomir Avatar answered Jan 30 '23 13:01

Titian Cernicova-Dragomir