如何将联合类型指定为对象键 TypeScript

11

我需要一种方法来输入一个对象,其中键是特定类型的“事件”字段的值,值是一个回调函数的数组,该函数接受相同类型的数据子类型的对象。

我已经尝试使用映射类型,但由于我是TypeScript的初学者,因此对此感到非常困惑。

// I have this type structure, where the event is always a string, but the data can be anything (but is constrained by the event)

interface EventTemplate {
  event: string;
  data: any;
}

export interface CreateEvent extends EventTemplate {
  event: 'create_game';
  data: {
    websocketID: 'string';
  };
}

export interface JoinEvent extends EventTemplate {
  event: 'join_game';
  data: {
    gameID: 'string';
  };
}

export interface MessageEvent extends EventTemplate {
  event: 'message';
  data: string;
}

export type WSEvent = CreateEvent | JoinEvent | MessageEvent;

// I want an object like this

type callbacks = {
  [key in WSEvent['event']]: ((data: WSEvent['data']) => void)[];
};

// Except that it forces the data structure to match with the key used. IE using a specific WSEvent rather than a generic one

// Something along the lines of:

type callbacks = {
  [key in (T extends WSEvent)['event']]: ((data: T['data']) => void)[];
};
// ...only valid..

const callbacks: callbacks = {
  // So this should be valid:
  message: [(data: MessageEvent['data']): void => {}, (data: MessageEvent['data']): void => {}],

  // But this should not be valid, as CreateEvent doesn't have the event 'join_game'
  join_game: [(data: CreateEvent['data']): void => {}],
};

如果这有帮助的话,我很高兴重构上面任何内容。


你已经到达了极限。现在需要生成代码。使用Node代码作为CLI,根据一些现有的代码生成你项目所需的接口。 - makeitmorehuman
2
@AliHabibzadeh OP还没有达到限制。TypeScript的条件和映射类型非常强大。看看我的答案 ;) - lukasgeiter
1个回答

13

我们需要的基本上是一种通过提供事件名称来查找整个事件类型的方法。这可以使用条件帮助器类型来实现。

type EventByName<E extends WSEvent['event'], T = WSEvent> = T extends {event: E} ? T : never;

第一个通用参数 E 必须是事件名称之一。第二个参数是我们要缩小范围的联合类型。它默认为 WSEvent,因此无需指定。条件表达式仅返回那些在联合类型中扩展了 {event: E}(其中 E 是事件名称)的事件。
一旦我们有了辅助类型,就很容易根据回调函数相应地调整现有的映射类型:
type Callbacks = {
  [E in WSEvent['event']]: ((data: EventByName<E>['data']) => void)[];
};

游乐场


有关回调函数名称的说明。建议使用PascalCase(帕斯卡命名法)命名类型,以便与变量区分开来。在我的示例中已经将其更改为Callbacks


非常感谢!这太完美了!解释非常清晰和有帮助。 - JForster
不用谢。考虑将答案标记为已接受,这样其他人就能清楚地知道您的问题已得到解决。 - lukasgeiter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接