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