【问题标题】:Typescript use generics to get type-value from interface using type-key parameter?打字稿使用泛型使用类型键参数从接口获取类型值?
【发布时间】:2021-02-14 03:50:09
【问题描述】:

我正在尝试在 Typescript 类型方面做得更好,并且需要类型安全的事件发射器。我现在尝试了许多不同的方法,但我似乎无法正确解决类型。你能看出我哪里出错了吗?

在下面的示例中,我有一个“事件”类型,它将事件名称映射到必须与该事件一起传递的参数。因此,如果我发出“Foo”,我还必须传递一个“bar”字符串,并且侦听器应该知道有一个“bar”属性要读取。

interface Events {
  Foo: {
    bar: string;
  }
}

type EventKeys = keyof Events

class Emitter {
  ...
  emit<K extends EventKeys> (title: K, value: Events[K]): void {
    // With this signature I want to require if the caller specifies a title of "Foo"
    // then they must specify value as "{bar: string}". This part looks to work great!
    this.emitter.emit("connection", [title, value])
  }

  public on (listener: any): void {
    // I use "any" here because this part of the code isn't super relevant to this example
    this.emitter.on('connection', listener.event.bind(f))
  }
}

class Listener {
  ...
  event<K extends EventKeys> ([title, value]: [title: K, value: Events[K]]): void {
    switch(title) {
      case "Foo":
        console.log(value)
        // Here "value" is of type "Events[K]", 
        // which I take to mean it should know it's type "Events[Foo]"
        // or actually "{bar: string}", 
        // but I don't get the autocompletion I expect.

        break
    }
  }
}

不可能从Events[K]这样的泛型中获取{bar: string}吗?

【问题讨论】:

    标签: typescript generics


    【解决方案1】:

    这里有你想要的东西。 emit() 现在是类型安全的,您可以按 Tab 键自动完成可能的事件名称。 需要 TypeScript 4+

    演示:https://repl.it/@chvolkmann/Typed-EventEmitter

    import { EventEmitter } from 'events'
    
    interface EventTree {
      Connected: {
        foo: string;
      };
    }
    type EventName = keyof EventTree;
    
    type FormattedEvent<E extends EventName> = [E, EventTree[E]];
    
    class MyListener {
      handleEvent<E extends EventName>([event, args]: FormattedEvent<E>) {
        console.log(`Event "${event}" happened with args`, args);
      }
    }
    
    class MyEmitter {
      protected emitter: EventEmitter
    
      constructor() {
        this.emitter = new EventEmitter()
      }
    
      emit<E extends EventName>(name: E, args: EventTree[E]) {
        this.emitter.emit('connection', [name, args]);
      }
    
      registerListener(listener: MyListener) {
        // The typing here is a bit overkill, but for demostration purposes:
        const handler = <E extends EventName>(fEvent: FormattedEvent<E>) => listener.handleEvent(fEvent)
        this.emitter.on('connection', handler)
      }
    }
    
    const emitter = new MyEmitter()
    emitter.registerListener(new MyListener())
    
    // This supports autocompletion
    // Try emitter.emit(<TAB>
    emitter.emit('Connected', { foo: 'bar' })
    
    // TypeScript won't compile these examples which have invalid types
    
    // > TS2345: Argument of type '"something else"' is not assignable to parameter of type '"Connected"'.
    // emitter.emit('something else', { foo: 'bar' })
    
    // > TS2345: Argument of type '{ wrong: string; }' is not assignable to parameter of type '{ foo: string; }'.
    // emitter.emit('Connected', { wrong: 'type' })
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-09
      • 2021-01-03
      • 2017-09-26
      • 2018-05-02
      • 2018-12-27
      • 1970-01-01
      • 1970-01-01
      • 2020-03-22
      相关资源
      最近更新 更多