Bagel03
Bagel03

Reputation: 715

Can I have a unknown number of types as "type parameters" to a class typescript

I am writing some typescript code, and came across this problem. After some google searches I couldn't find anything that would work, however the problem is kinda hard to explain without code, so here's the specific code I'm trying to make work:


export class Signal<T> {
    public readonly listeners: ((arg: T) => any)[];

    constructor(){
        this.listeners = [];
    }

    addListener(listener: (arg: T) => any) {
        assert(!this.listeners.includes(listener), "Signal listener was added twice")
        this.listeners.push(listener);
    }

    removeListener(listener: (arg: T) => any) {
        const index = this.listeners.indexOf(listener);
        if(index != -1){
            this.listeners.splice(index, 1)
        }
    }

    dispatch(arg: T){
        for(let i = this.listeners.length - 1; i > -1; i--){
            this.listeners[i](arg);
        }
    }
}

This is just a typed signal, vary similar to an event listener. You could use it like this:

const strSignal = new Signal<string>();
strSignal.addListener((str /* TSC now knows this is a string */) => console.log(str));
strSignal.dispatch("Hello World");

However, my problem is with more complex callbacks, where I want more than one parameter:

const complexSignal = new Signal<string, number>();
complexSignal.addListener((str /* string */, num /* type error */) => console.log(str + (num * 2)));
complexSignal.dispatch("Hello World", 3);

Obviously, the second type inside the angle brackets is ignored, and then the function passed into addListener doesn't fit the required type ((arg: T) => any), throwing a type error.

What I would like to do would be to use something similar to spread arrays, maybe like this:


export class Signal<...T> {
    public readonly listeners: ((...args: T) => any)[];

    constructor(){
        this.listeners = [];
    }

    addListener(listener: (...args: T) => any) {
        this.listeners.push(listener);
    }

    removeListener(listener: (...args: T) => any) {
        const index = this.listeners.indexOf(listener);
        if(index != -1){
            this.listeners.splice(index, 1)
        }
    }

    dispatch(...args: T){
        for(let i = this.listeners.length - 1; i > -1; i--){
            this.listeners[i](...args);
        }
    }
}

This would then be used like this:

const complexSignal = new Signal<string, number>();
complexSignal.addListener((str /* T[0] = string */, num /* T[1] = number */) => console.log(str + (num * 2)));
complexSignal.dispatch("Hello World", 3);

Typescript does not to seem to support this though, so what my question boils down to is:

Can I replicate the behavior of the second signal class (Have a unknown number of types as "type parameters" to a class) in valid code?

Upvotes: 0

Views: 264

Answers (1)

user6572277
user6572277

Reputation:

You can do this using tuple types, with no change to your existing class.

// Before
const complexSignal = new Signal<string, number>();
complexSignal.addListener((str /* T[0] = string */, num /* T[1] = number */) => console.log(str + (num * 2)));
complexSignal.dispatch("Hello World", 3);

// After
const complexSignal = new Signal<[string, number]>();
complexSignal.addListener(([str /* T[0] = string */, num /* T[1] = number */]) => console.log(str + (num * 2)));
complexSignal.dispatch(["Hello World", 3]);

Check on TypeScript playground

Upvotes: 1

Related Questions