TypeScript: динамически объявленные методы в классе - PullRequest
0 голосов
/ 31 марта 2020

У меня есть такой код:

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

Теперь, когда я использую экземпляр класса, TypeScript жалуется, когда я звоню relayer.foo() или relayer.bar(). Чтобы компилировать код, я должен привести его as any или аналогичный.

У меня есть интерфейс, который объявляет foo, bar и другие методы:

interface MyInterface {
  foo: (a: string) => Promise<string>
  bar: (b: string, c: number) => Promise<string>
  // ... 20 other methods
}

Как получить TypeScript для изучения динамически объявленных методов класса foo и bar? Может ли здесь использоваться синтаксис declare?

Ответы [ 3 ]

1 голос
/ 10 апреля 2020

Первым шагом является создание типа или интерфейса, в котором при индексации значением в 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, пропуская первый шаг. Это также обеспечивает безопасность типов в ваших вызовах.

1 голос
/ 31 марта 2020

Используйте следующий синтаксис:

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

0 голосов
/ 31 марта 2020

Для меня это звучит как необходимость в интерфейсе.

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
}

Похоже, что вы делаете, реализует какой-то своего рода интерфейс. В своем конструкторе вы определяете, каковы реализации метода, а не определяете их в теле класса. Может помочь читать Интерфейсы типов классов

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...