在TypeScript中扩展Tagged / Discriminated Union在不同的模块中?

时间:2017-09-24 17:15:36

标签: typescript strong-typing discriminated-union

我有一个系统用于通过套接字连接来回传递JSON消息。它使用Tagged Unions作为消息类型:

export type ErrorMessage = { kind: 'error', errorMessage: ErrorData };
export type UserJoined = { kind: 'user-joined', user: UserData };
// etc
export type Message = ErrorMessage | UserJoined | /*etc*/;

它在基本代码中运行得相当不错,但我有一个基于它构建的模块,我想扩展代码。我有一个要添加的新消息类型:

export type UserAction = { kind: 'user-action', action: Action }

问题在于我无法扩展“Message”以将新的UserAction包含到union中。我想我可以发表自己的扩展信息:

export type ExtendedMessage = Message | UserAction;

但这里的问题似乎是笨重,排名第一。我不能将我的新UserAction传递给任何期望消息的方法,即使代码实际上应该完全正常工作。其他任何想要扩展我的模块和基本模块的人都需要创建第三种类型:export type ExtendedMessageAgain = ExtendedMessage | MyNewMessage

因此。我已经看到通过添加新的.d.ts文件扩展了其他属性的接口(比如护照如何扩展Express JS的Request对象以添加身份验证属性),我认为这样的东西也必须存在于标记的联合,对吧? / p>

但事实似乎并非如此。我四处搜索,并没有看到这种模式在任何地方使用。这让我相信也许我的设计在某种程度上是错误的。但我没有看到解决方法。

我不想使用类,因为类型信息是通过线路擦除的; kind属性必须存在。我喜欢这种范式:

declare var sendMessage = (message: Message) => void;
sendMessage( { kind: 'error', errorMessage: { /* */ } }); // ok
sendMessage( { kind: 'random', parameter: { /* */ } }); // error, no kind 'random'
sendMessage( { kind: 'error', message: { /* */ } }); // error, no property 'message' on 'error'

但我所看到的唯一解决方案是将Message作为接口基础,如下所示:

export interface Message { kind: string }
export interface ErrorMessage extends Message { errorMessage: ErrorData }

declare var sendMessage = (message: Message) => void;

sendMessage( { kind: 'error', errorMessage: { /* */ } }); // ok
sendMessage( { kind: 'random', parameter: { /* */ } }); // ok
sendMessage( { kind: 'error', message: { /* */ } }); // ok

这种方法失去了上面所有类型的保护。

那么......有没有办法在多个模块中扩展Tagged Unions,影响该类型的原始名称,而无需定义新类型?或者这里有一个更好的设计,我只是没有看到?

以下是启发此帖子的代码:https://github.com/RonPenton/NotaMUD/blob/master/src/server/messages/index.ts

我正在寻求大规模重构,因此我可以将所有消息移到单独的模块中,而不是随着时间的推移,这个文件逐渐变得不合时宜。

1 个答案:

答案 0 :(得分:0)

您可以执行以下操作来定义Message的并集类型:

export interface MessageTypes {}
export type Message = MessageTypes[keyof MessageTypes]

然后在定义新消息类型的任何地方执行以下操作:

export type UserAction = { kind: 'user-action', action: Action }

declare module '../message' { // Where you define MessageTypes
  interface MessageTypes {
    UserAction: UserAction
  }
}

因此MessageTypes接口的值成为联合类型,您可以使用声明合并将更多值添加到接口,这将自动更新联合类型。

您可以查看TS文档以获取有关声明合并的更多信息:https://www.typescriptlang.org/docs/handbook/declaration-merging.html