Первым шагом является создание типа или интерфейса, в котором при индексации значением в methodsList
результатом будет функция:
// 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 }
Затем в конструкторе можно будет назначить Клавиши methodsList
, вы можете добавить утверждение типа, что 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) => {
// ...
}
}
}
}
Теперь, после построения, вы должны привести тип еще:
const relayer = new Relayer() as Relayer & HasMethods
relayer.foo('asd')
relayer.bar('jkl', 123)
Вы можете также избавьтесь от приведений при построении с использованием фабричной функции:
export class Relayer {
constructor() {
// As above
}
static construct(): Relayer & HasMethods {
return new Relayer() as Relayer & HasMethods
}
}
const relayer = Relayer.construct()
Другой способ - создать новый класс и утверждать тип, что new
приводит к объекту HasMethods
:
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)
Или, если вы используете только new
и затем методы в methodsList
, вы можете сделать:
export const Relayer = class Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// ...
}
}
}
} as { new (): HasMethods };
Вы также можете использовать вместо этого свой интерфейс MyInterface
HasMethods
, пропуская первый шаг. Это также обеспечивает безопасность типов в ваших вызовах.