在不同的模块中扩大 TypeScript 中的 Tagged/Discriminated 联合?

Widen Tagged/Discriminated Union in TypeScript in a different module?

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

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 包含到联合中。我想我可以制作自己的扩展消息:

export type ExtendedMessage = Message | UserAction;

但这里的问题是它看起来很笨重,第一。我无法将我的新 UserAction 传递到任何需要消息的方法中,即使代码实际上应该可以正常工作。以后想要扩展我的模块和基本模块的任何其他人都需要创建第三种类型:export type ExtendedMessageAgain = ExtendedMessage | MyNewMessage.

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

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

我不想使用 类 因为类型信息通过网络被删除; 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

并且此方法失去了上面所有的良好类型保护。

所以...有没有办法在多个模块中扩展标记联合,影响类型的原始名称,而不定义新类型?还是这里有我没看到的更好的设计?

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

我正在寻求大规模重构,以便我可以将所有消息移出到单独的模块中,而不是随着时间的推移这个文件变得一团糟。

您可以这样做来定义 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