首先是一些术语...... TypeScript 中的某些术语对之间存在双重性,并且经常混淆。 “扩展”一词通常是指缩小一个类型以使其更多受到约束。就您而言,当您说要“扩展”联合时,您的意思与extends 的含义不同。相反,您希望加宽类型以使其更少受到约束。在 TypeScript 中没有一个很好的类似术语......你可以在 Java 之类的东西中称它为super。无论如何,要明确的是,您希望在您的库中有一个空的联合,并允许其他模块向其中添加成分,从而扩大(而不是扩展)它。
有一个名为declaration merging 的语言功能允许您通过“重新打开”模块/命名空间/接口并向它们添加属性/方法来扩充现有类型。乍一看似乎没有帮助,因为您要更改的类型是类型别名(即type X = ...)并且您无法重新打开它们。而且你也不想给它添加任何属性/方法。
这里的技巧是创建一个类似CommandHandlerMap 的接口,其属性键是虚拟值,其属性值是您正在寻找的CommandHandler 联合的元素。然后你可以定义type CommandHandler = CommandHandlerMap[keyof CommandHandlerMap]。通过合并到模块中的CommandHandlerMap,您将自动使CommandHandler union 获得一个组成部分。
代码可能如下所示:
library.ts
export type Command = { AggregateId: string };
export type CommandHandlerResult = { streamId: string };
export interface CommandHandlerMap {
// will merge into this interface
}
export type CommandHandler = CommandHandlerMap[keyof CommandHandlerMap]
addUser.ts
import * as Library from './library';
export interface AddUserCommand extends Library.Command {
username: string;
somethingElse: number;
}
export type AddUser =
(command: AddUserCommand) => Promise<Library.CommandHandlerResult>;
// reopen the CommandHandlerMap interface in the library module
declare module './library' {
export interface CommandHandlerMap {
AddUser: AddUser // add this
}
}
removeUser.ts
import * as Library from './library';
export interface RemoveUserCommand extends Library.Command {
username: string;
withExtremePrejudice: boolean;
}
export type RemoveUser =
(command: RemoveUserCommand) => Promise<Library.CommandHandlerResult>;
// reopen the CommandHandlerMap interface in the library module
declare module './library' {
export interface CommandHandlerMap {
RemoveUser: RemoveUser // add this
}
}
然后你可以验证这个是否有效:
index.ts
import * as Library from './library';
import { RemoveUser, RemoveUserCommand } from './removeUser';
const handler: Library.CommandHandler = async (c: RemoveUserCommand) => {
return {
streamId: c.username
}
}; // okay
// const handler: AddUser | RemoveUser
就目前而言,这很有效。可以看到Library.CommandHandler被理解为AddUser | RemoveUser。请注意,CommandHandlerMap keys 恰好是 "AddUser" 和 "RemoveUser",但这不是必需的。我们本可以使用键 "BlahBlah" 或 "!!!@#$" 合并到 CommandHandlerMap。它们是虚拟键,只需要唯一(因为您不想与现有键发生冲突)即可工作。
此方法的主要警告可能是,您在模块中引入的任何错误最终都可能在库代码而不是您的模块中被标记。例如,如果您的库中的代码假定 CommandHandler 绝对是一种函数类型,并且模块中的某个人将非函数值合并到联合中,您可以预期错误会出现在库中。这有点难以调试。
无论如何,希望能给你一些想法。祝你好运!
Link to code