【问题标题】:How can we map an array of strings to a TypeScript type map of keys to types?我们如何将字符串数组映射到类型键的 TypeScript 类型映射?
【发布时间】:2018-11-29 21:48:58
【问题描述】:

我有以下工作代码,没有类型错误:

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 ) {}

(example on 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 中,我可以创建类似枚举的对象,而不必诉诸一些不那么 DRY 的东西

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 中尽可能保持 DRY,只定义每个事件的名称一次,而且还有一个与事件有效负载相关联的类型。

我可以使用以下 less-DRY 方式,每次重复事件名称 3 次:

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 ) {}

(Playground link)

所以我的问题是:有没有一种方法可以编写这个,以便我每次只指定一次事件以及有效负载类型?

如果不可能只使用每个事件名称的一个实例,那么最好的方法是什么?

【问题讨论】:

  • "自动完成类型名称" TypeScript 将在字符串文字中为您提供自动完成功能;你根本不需要那个。
  • 您可以将纯 JS 解决方案与类型转换一起使用。
  • @SLaks 看起来怎么样?
  • const Events = {} as {[k in keyof Events]: k}
  • 但是我们如何将事件名称粘贴到空对象中,使其类似于枚举,而不在代码中重复事件名称?我找到了一种使用虚拟课程的方法(请参阅我的答案)。有没有更好的办法?我想也许这会有所帮助:gist.github.com/jcalz/381562d282ebaa9b41217d1b31e2c211

标签: typescript mapped-types


【解决方案1】:

我找到了一种保持 DRY 的方法,以便每个事件名称只定义一次,使用一个包含类型信息的虚拟类并生成我们可以迭代的 JS 输出,以便使用正确的类型创建类似枚举:

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') }

playground link

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-08-08
    • 2018-02-05
    • 2023-04-04
    • 1970-01-01
    • 2021-06-01
    • 1970-01-01
    • 2012-01-23
    • 2020-05-16
    相关资源
    最近更新 更多