Reputation: 6471
I've some code like:
const methodsList = [
'foo',
'bar',
// ... 20 other items ...
]
export class Relayer {
constructor() {
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// console.log('relaying call to', methodName, args)
// this is same for all methods
}
}
}
}
const relayer = new Relayer()
relayer.foo('asd') // TS error
relayer.bar('jkl', 123) // TS error
Now when I use the class instance, TypeScript complains when I call relayer.foo()
or relayer.bar()
. To make the code compile, I've to cast it as any
or similar.
I've an interface that declares foo
, bar
and the other methods:
interface MyInterface {
foo: (a: string) => Promise<string>
bar: (b: string, c: number) => Promise<string>
// ... 20 other methods
}
How do I get TypeScript to learn the dynamically declared foo
and bar
class methods? Can the declare
syntax be useful here?
Upvotes: 2
Views: 2535
Reputation: 40811
First step is to create a type or interface where when indexed by a value in methodsList
, the result will be a function:
// The cast to const changes the type from `string[]` to
// `['foo', 'bar']` (An array of literal string types)
const methodsList = [
'foo',
'bar'
] as const
type HasMethods = { [k in typeof methodsList[number]]: (...args: any[]) => any }
// Or
type MethodNames = typeof methodsList[number] // "foo" | "bar"
// k is either "foo" or "bar", and obj[k] is any function
type HasMethods = { [k in MethodNames]: (...args: any[]) => any }
Then, in the constructor, to be able to assign the keys of methodsList
, you can add a type assertion that this is HasMethods
:
// General purpose assert function
// If before this, value had type `U`,
// afterwards the type will be `U & T`
declare function assertIs<T>(value: unknown): asserts value is T
class Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
// `methodName` has type `"foo" | "bar"`, since
// it's the value of an array with literal type,
// so can index `this` in a type-safe way
this[methodName] = (...args) => {
// ...
}
}
}
}
Now after constructing, you have to cast the type still:
const relayer = new Relayer() as Relayer & HasMethods
relayer.foo('asd')
relayer.bar('jkl', 123)
You can also get rid of the casts when constructed using a factory function:
export class Relayer {
constructor() {
// As above
}
static construct(): Relayer & HasMethods {
return new Relayer() as Relayer & HasMethods
}
}
const relayer = Relayer.construct()
Another way around it is to create a new class and type-assert that new
results in a HasMethods
object:
class _Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// ...
}
}
}
}
export const Relayer = _Relayer as _Relayer & { new (): _Relayer & HasMethods }
const relayer = new Relayer();
relayer.foo('asd')
relayer.bar('jkl', 123)
Or if you are only using new
and then methods in methodsList
, you can do:
export const Relayer = class Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// ...
}
}
}
} as { new (): HasMethods };
You can also use your MyInterface
interface instead of HasMethods
, skipping the first step. This also gives type-safety in your calls.
Upvotes: 7
Reputation: 130
Use the following syntax:
export class Relayer {
constructor() {}
public foo(){
// your foo method
this.executedOnEachFunction();
}
public bar(){
// your bar method
this.executedOnEachFunction();
}
executedOnEachFunction(){
// what you want to do everytime
}
}
https://repl.it/repls/LawfulSurprisedMineral
Upvotes: 1
Reputation: 2107
To me, this sounds like a need for an interface.
interface MyInterface {
foo(): void; // or whatever signature/return type you need
bar(): void;
// ... 20 other items ...
}
export class Relayer implements MyInterface {
constructor() {}
foo(): void {
// whatever you want foo to do
}
// ... the rest of your interface implementation
}
What it looks like you are doing is implementing some interface of sorts. In your constructor you are defining what the method implementations are instead of defining them in the class body. Might help to read Class Type Interfaces
Upvotes: 0