Как мы можем сопоставить массив строк с картой типов TypeScript ключей для типов?

У меня есть следующий рабочий код без ошибок типа:

type Events = { SOME_EVENT: number; OTHER_EVENT: string }

interface EventEmitter<EventTypes> {
  on<K extends keyof EventTypes>(s: K, listener: (v: EventTypes[K]) => void);
}

declare const emitter: EventEmitter<Events>;

emitter.on('SOME_EVENT', (payload) => testNumber(payload));
emitter.on('OTHER_EVENT', (payload) => testString(payload));

function testNumber( value: number ) {}
function testString( value: string ) {}

(пример на TS Playground)

Однако я хотел бы использовать что-то похожее на enum, чтобы иметь автозаполнение имен типов, чтобы я мог написать что-то вроде следующего вместо использования строковых литералов:

emitter.on(EventNames.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(EventNames.OTHER_EVENT, (payload) => testString(payload));

Я пытаюсь сохранить вещи сухими, поэтому мне интересно, есть ли способ заставить это работать, не повторяя все имена событий в новом типе.

В простом JavaScript я могу легко сделать следующее:

const EventNames = [
    'SOME_EVENT',
    'OTHER_EVENT',
]

const Events = {}

for (const eventName of Events) {
    Events[eventName] = eventName
}

// then use it:

emitter.on(Events.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(Events.OTHER_EVENT, (payload) => testString(payload));

Таким образом, в простом JS я могу создавать объекты, подобные перечислению, без необходимости прибегать к чему-то менее СУХОМУ, например

const Events = {
    SOME_EVENT: 'SOME_EVENT', // repeats the names twice
    OTHER_EVENT: 'OTHER_EVENT',
}

emitter.on(Events.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(Events.OTHER_EVENT, (payload) => testString(payload));

Мне интересно, как я могу сохранить вещи как можно более сухими в TypeScript, только когда-либо определяя имя каждого события только один раз, но также имея тип, связанный с полезными нагрузками события.

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

type Events = { SOME_EVENT: number; OTHER_EVENT: string }

const EventNames: {[k in keyof Events]: k} = {
  SOME_EVENT: 'SOME_EVENT', OTHER_EVENT: 'OTHER_EVENT'
}

interface EventEmitter<EventTypes> {
  on<K extends keyof EventTypes>(s: K, listener: (v: EventTypes[K]) => void);
}

declare const emitter: EventEmitter<Events>;

emitter.on(EventNames.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(EventNames.OTHER_EVENT, (payload) => testString(payload));

function testNumber( value: number ) {}
function testString( value: string ) {}

(ссылка на игровую площадку )

Итак, мой вопрос: есть ли способ написать это так, чтобы я указывал события только один раз каждое, а также типы полезной нагрузки?

Если это невозможно сделать только с одним экземпляром каждого имени события, как лучше всего?


person trusktr    schedule 29.11.2018    source источник
comment
чтобы иметь автозаполнение имен типов, TypeScript даст вам автозаполнение внутри строковых литералов; тебе это совсем не нужно.   -  person SLaks    schedule 30.11.2018
comment
Вы можете использовать свое простое решение JS с приведением типов.   -  person SLaks    schedule 30.11.2018
comment
@SLaks Как это может выглядеть?   -  person trusktr    schedule 30.11.2018
comment
Но как вставить имена событий в пустой объект, чтобы он был похож на перечисление, не повторяя имена событий в коде? Я нашел один способ сделать это с фиктивным классом (см. мой ответ). Есть ли способ лучше? Я думаю, может быть, это поможет: gist.github.com/jcalz/381562d282ebaa9b41217d1b31e2c211   -  person trusktr    schedule 30.11.2018
comment
@SLaks (см. предыдущий комментарий) Я не думаю, что идея с кортежем сработает. Как еще я могу это сделать, написав каждое имя события не более одного раза?   -  person trusktr    schedule 30.11.2018
comment
Использование цикла, как в JavaScript.   -  person SLaks    schedule 30.11.2018
comment
@SLaks Я не понимаю, как. Не могли бы вы показать пример в качестве ответа?   -  person trusktr    schedule 30.11.2018
comment
Ой; Я не заметил, что EventTypes был type. Ваш ответ кажется лучшим вариантом.   -  person SLaks    schedule 30.11.2018


Ответы (1)


Я нашел способ сохранить вещи СУХИМИ, чтобы каждое имя события определялось только один раз, используя фиктивный класс, который содержит информацию о типе И генерирует вывод JS, который мы можем повторять, чтобы создать enum-подобный с правильной типизацией:

class EventTypes {
    constructor(
        // define a map of event names to event payload types here.
        public SOME_EVENT: number,
        public OTHER_EVENT: string
    ) {}
}

const EventNames = {} as { [k in keyof EventTypes]: k }

for (const key in new (EventTypes as any)) {
    EventNames[key] = key
}

console.log(EventNames)

interface EventEmitter<EventTypes> {
  on<K extends keyof EventTypes>(s: K, listener: (v: EventTypes[K]) => void);
}

declare const emitter: EventEmitter<EventTypes>;

emitter.on(EventNames.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(EventNames.OTHER_EVENT, (payload) => testString(payload));

function testNumber( value: number ) { console.assert(typeof value === 'number') }
function testString( value: string ) { console.assert(typeof value === 'string') }

ссылка на игровую площадку

person trusktr    schedule 29.11.2018