#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. Спасибо! Похоже, это лучшее решение.