簡體   English   中英

Typescript 使用 generics 使用類型鍵參數從接口獲取類型值?

[英]Typescript use generics to get type-value from interface using type-key parameter?

我試圖在 Typescript 類型上做得更好,並且需要一個類型安全的事件發射器。 我現在嘗試了許多不同的方法,但我似乎無法正確解決類型。 你能看出我哪里出錯了嗎?

在下面的示例中,我有一個“事件”類型,它將事件名稱映射到必須與該事件一起傳遞的 arguments。 因此,如果我發出“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]一樣從 generics 獲得{bar: string}

這里有一些你想要的東西。 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' })

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM