Создание типа обработчика динамических событий - PullRequest
0 голосов
/ 09 ноября 2018

Я хочу, чтобы все, что расширяет мой компонентный класс, могло иметь обработчики событий с проверкой типа, используя имя метода для определения типа события, которое они обрабатывают.

class UIEvent { }
class MouseInput extends UIEvent { }
class KeyboardInput extends UIEvent { }

let Events = {
    MouseInput,
    KeyboardInput,
}

Затем я использую сопоставленные типы для создания типа EventTarget, который определяет сопоставление от имени к типу обработчика событий.

type EventTarget = {
    [K in keyof typeof Events]?:
      (event: InstanceType<typeof Events[K]>) => void;
}

EventTarget может обрабатывать столько или мало типов событий, сколько они хотят, поэтому методы обработчика событий являются необязательными.

Затем я пытаюсь использовать это в классе компонентов, но не могу заставить компилятор принудительно реализовать интерфейс.

class Component implements EventTarget {
    // The compiler has no problems with this, even though it violates
    // the EventTarget interface...
    MouseInput(event: KeyboardInput) {

    }
}

Я сильно подозреваю, что пытаюсь здесь работать против структурной типизации, потому что, как только я добавляю свойство, отличающее MouseEvent от KeyboardEvent, проверки типов работают.

class UIEvent<T> { t: T }
class MouseInput extends UIEvent<"MouseInput"> {}
class KeyboardInput extends UIEvent<"KeyboardInput"> {}

Это работает, но я бы предпочел использовать конструктор события в качестве дискриминатора, а не добавлять отдельный тип свойства.

И, наконец, я хочу расширить этот контракт на подтипы класса компонента.

class Clickable extends Component {
    // This should also fail to typecheck, but interfaces aren't inherited
    MouseInput(event: KeyboardInput) {

    }
}

Самое близкое, что я могу получить прямо сейчас, - это явным образом реализовать каждый подтип EventTarget.

1 Ответ

0 голосов
/ 09 ноября 2018

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

Я бы сказал, что ваш дизайн, вероятно, будет включать некоторые свойства, которые сделают их несовместимыми, помимо простого доказательства концепции. Так что это уже даст ошибку, как и ожидалось:

export class UIEvent { }
class MouseInput extends UIEvent { public x!: number; public y!: number }
class KeyboardInput extends UIEvent { public key!: string }

let Events = {
    MouseInput,
    KeyboardInput,
}

type EventTarget = {
    [K in keyof typeof Events]?:
      (event: InstanceType<typeof Events[K]>) => void;
}

class Component implements EventTarget {
    MouseInput(event: KeyboardInput) { // Error now 

    }
}

Если вы не хотите предоставлять какой-либо элемент, вы можете просто объявить private элемент типа undefined. Это сделает типы несовместимыми, но не окажет влияния на время выполнения:

class MouseInput extends UIEvent { private isMouse: undefined }
class KeyboardInput extends UIEvent { private isKeyboard: undefined }
...