I have a function which binds a passed function to a HTMLElement:
global.d.ts:
type tPrimitivRestParams = (number | object | string)[];
type tDefaultVoidFunction = (...params: tPrimitivRestParams) => void;
export default class AwesomeFunctions {
public static addClickCallback(id: HTMLElement, func: tDefaultVoidFunction, scope: object, ...argArray: tPrimitivRestParams): void {
[...] // checks
// eslint-disable-next-line no-null/no-null
element.onclick = func ? func.bind(scope, ...argArray) : null;
}
}
export default class AwesomeClass {
private bindMe(id: string):void {}
private bindMeToo():void {}
private bindMeThree(a:string, b: number, c: object): void {}
private bindMeFour(clazz: SomeClass): void {}
private binder():void {
AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMeToo, this); // TS says yes
AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMe, this, 'param'); // TS says no
AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMeThree, this, 'param', 2, {whoopi: 'goldberg', age: 24, 'actual-age': 'dontcha dare askn!!!'}); // TS says no
AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMeThree, this, new SomeClass()); // TS says no
}
}
It's working fine with JS but TS yells for all but the first:
// Example for bindMe
S2345: Argument of type '(id: string) => void' is not assignable to parameter of type 'tDefaultVoidFunction'.
If I do this:
AwesomeFunctions.addClickCallback(..., this.bindMe as tDefaultVoidFunction , ...);
TS is satisfied, but it seems wrong to me.
I know it's a problem with the tDefaultVoidFunction type. Is it possible to define a type to make TS happy with all the bindings? I can't use any, null or Function.
I also could do this:
AwesomeFunctions.addClickCallback(..., () => this.bindMe('param'), this);
TS is also satisfied but 95% of the addClickCallback function gets pointless. But that's not the reason for my hesitation of using an arrow function. I fear the scopes could differ. As far as I can tell, the scope is always a this. So in this case I claim the arrow function and the bind act the same. Am I wrong?
Reproducible example:
type tPrimitivRestParams = (number | object | string)[];
type tDefaultVoidFunction = (...params: tPrimitivRestParams) => void;
class AwesomeFunctions {
public static addClickCallback(id: string, func: tDefaultVoidFunction, scope: object, ...argArray: tPrimitivRestParams): void {
// DO something
}
}
class AwesomeClass {
private bindMe(id: string):void {}
private bindMeToo():void {}
private bindMeThree(a:string, b: number, c: object): void {}
private binder():void {
AwesomeFunctions.addClickCallback('A', this.bindMeToo, this);
AwesomeFunctions.addClickCallback('B', this.bindMe, this, 'param');
AwesomeFunctions.addClickCallback('C', this.bindMeThree, this, 'param', 2, {whoopi: 'goldberg', age: 24, 'actual-age': 'dontcha dare askn!!!'});
}
}
Playground: TS Example
That's what you tell the compiler:
type tDefaultVoidFunction = (...params: (number | object | string)[]) => void;
The callback must accept any number of parameters where each parameter must accept number | object | string.
Let's have a look at your function...
const example: tDefaultVoidFunction = (id: string): void {}
The parameter accepts only strings and so the compiler will tell you that's wrong. (number and object missing)
You can define multiple allowed signatures by using union types:
type tDefaultVoidFunction = ((id: string) => void)
| (() => void)
| ((a: string, b: number, c: object) => void)
| ((clazz: SomeClass) => void);
Notes:
private function bindMe(id: string):void {}
is no valid TypeScript code inside of classes, I guess it's just a typo.Possible option:
Instead of writing:
AwesomeFunctions.addClickCallback(document.getElementById('...'), this.bindMeToo, this);
use:
document.getElementById('...')?.addEventListener('click', (e) => this.bindMeTo());
// ignoring that we do not provide valid parameters in both cases