Машинопись для типичного шаблона наблюдателя

#typescript #generics #observers

#typescript #дженерики #наблюдатели

Вопрос:

Я пытаюсь написать машинописный текст для структуры, подобной шаблону наблюдателя, и мне это не удается. Я думаю, что я почти закончил, но по какой-то причине я не понимаю, почему ts жалуется.

 type OnChangeListener = (object: object, from: string, to: string) => void
type OnCreateListener = (object: object) => void

type ListenerTypes = {
    onChange: OnChangeListener,
    onCreate: OnCreateListener
}

type Listener<T extends keyof ListenerTypes> = ListenerTypes[T]

type Listeners = {
    onChange: OnChangeListener[],
    onCreate: OnCreateListener[]
}

class SomeClass {
    private listeners: Listeners = {
        onChange: [],
        onCreate: []
    }

    public create(object: object) {
        this.listeners.onCreate.forEach(listener => listener(object))
    }

    public change(object: object, from: string, to: string) {
        this.listeners.onChange.forEach(listener => listener(object, from, to))
    }

    public registerListener<T extends keyof ListenerTypes>(name: T, listener: Listener<T>): void
    {
        this.listeners[name].push(listener) // <-- here is a problem
        /*
Argument of type 'Listener<T>' is not assignable to parameter of type 'OnCreateListener'.
  Type 'OnChangeListener | OnCreateListener' is not assignable to type 'OnCreateListener'.
    Type 'OnChangeListener' is not assignable to type 'OnCreateListener'
        */
    }
}

const someObject = new SomeClass()

someObject.registerListener("onChange", (o, from, to) => {}) // <-- this is ok
someObject.registerListener("onCreate", (o) => {}) // <-- this is ok
someObject.registerListener("onCreate",  (o, from, to) => {}) // <-- this error is ok, as the callback should be different
 

Когда я меняю тип слушателей на

 type Listeners = {
    onChange: any[],
    onCreate: any[]
}
 

тогда функция register работает нормально, но триггер не показывает какой-либо тип для объекта прослушивателя в forEach, поскольку это очевидно. Так что есть вариант использовать то, что мне не нравится. Что я здесь делаю не так?

Ответ №1:

Если вы посмотрите на примеры типов индексов в документах, кажется, что общее индексирование работает только тогда, когда тип индексируемого объекта сам по себе является частью общего контекста функции, в отличие от конкретного объекта вне его.

Внутри registerListener функции ожидается, что тип listener in this.listeners[name].push(listener) будет пересечением OnChangeListener и OnCreateListener , поскольку он не знает name , что будет "onChange" или "onCreate" , и, таким образом, будет принимать только сигнатуры функций, которые применимы к обоим. Typescript не выполняет универсальную индексную магию, если только тип объекта также не является универсальным.

Поскольку registerListener правильно распознает сигнатуру прослушивателя, которая должна быть передана для данного ключа, я думаю, что лучшее решение — просто сообщить Typescript, что массив прослушивателя, к которому вы обращаетесь, соответствует переданному прослушивателю.

 
public registerListener<T extends keyof ListenerTypes>(name: T, listener: Listener<T>): void {
    (this.listeners[name] as Listener<T>[]).push(listener);  
}
 

Комментарии:

1. Спасибо! Похоже, это лучшее решение.