typescript - 从枚举接口(interface)上的 prop 解析类型

标签 typescript typescript-generics

所以我有一个大型网络应用程序。它解决了很多实体问题。 例如:

enum EntityTypes {
  EntityA = 'A',
  EntityB = 'B',
  EntityC = 'C',
  EntityD = 'D',
}

interface EntityA {
  type: EntityTypes.EntityA;
  aSpecialProperty: boolean;
}

interface EntityB {
  type: EntityTypes.EntityB;
  bSpecialProperty: boolean;
}

interface EntityC {
  type: EntityTypes.EntityC;
  cSpecialProperty: string;
}

interface EnityD {
  type: EntityTypes.EntityD;
  dSpecialProperty: number;
}

我们这样做是因为我们稍后想要解析像这样的每种类型所独有的属性(到目前为止我们都是这样做的):

type EntityTypesResolver<T extends EntityTypes> = T extends EntityTypes.EntityA
  ? EntityA
  : T extends EntityTypes.EntityB
  ? EntityB
  : T extends EntityTypes.EntityC
  ? EntityC
  : T extends EntityTypes.EntityD
  ? EntityD
  : never;

type EntityResolver<ResultType> = {
  [P in EntityTypes]: (entity: EntityTypesResolver<P>) => ResultType;
};

const resolver: EntityResolver<string> = {
  [EntityTypes.EntityA]: (entity) => `${entity.aSpecialProperty}`,
  [EntityTypes.EntityB]: (entity) => `${entity.bSpecialProperty}`,
  [EntityTypes.EntityC]: (entity) => entity.cSpecialProperty,
  [EntityTypes.EntityD]: (entity) => `${entity.dSpecialProperty}`,
};

问题:我想消除(或找到更优雅的解决方案)EntityTypesResolver,因为它看起来像是一个容易出错的代码块,原因如下:

  1. 由于我们是一个致力于此代码库的大型团队,我不想依赖其他程序员记住维护此类型
  2. 这里的可读性很糟糕

我花了很多时间试图找到一个优雅的解决方案,但似乎没有取得什么成果。

@jcalz提出的解决方案: 我对这个解决方案的问题是我仍然必须依赖其他程序员来维护 Entity 类型别名。 //这两行

type Entity = EntityA | EntityB | EntityC | EntityD
type EntityTypesResolver<T extends EntityTypes> = Extract<Entity, { type: T }>

最佳答案

您的EntityTypesResolver<T> type 函数根据 EntityTypes 的成员从一组已知接口(interface)中选择一个接口(interface)通过 T 。目前,它通过一系列 conditional types 来实现这一点。这是可行的,但正如你所说,它的可读性不是很好。

将这些接口(interface)变成 discriminated union 更加自然和可读。并定义EntityTypesResolver Extract 该联盟的一名或多名成员基于 type判别性属性(property)。看起来像这样:

type Entity = EntityA | EntityB | EntityC | EntityD;
type EntityTypesResolver<T extends EntityTypes> = Extract<Entity, { type: T }>;

这样可读性问题就解决了。


不幸的是,编译器无法知道哪一组接口(interface) EntityTypesResolver 是无法回避的事实。应该咨询,除非你告诉它。因此,必须有人维护 Entity类型,确保它包含与 EntityTypes 的每个成员相对应的成员。枚举。例如,无法要求编译器扫描它知道的所有接口(interface)并选择具有 type 的接口(interface)。正确的属性。

您可以做的一件事(感谢您的建议)是编写一些代码,当且仅当 Entity 时才会生成编译器警告。 union 缺少任何 EntityTypes来自其成员的枚举值。一种方法是定义一个虚拟对象 generic type其类型参数为 constrained致所有type的联盟属性 Entity ,其中 defaultsEntityTypes ,这是所有 EntityTypes 的并集枚举值。通用参数默认值必须扩展其约束,因此这行代码用于检查是否 Entity已完成:

type EntityCompletenessCheck<T extends Entity['type']
  = EntityTypes> = void;
//  ^^^^^^^^^^^ <-- is there an error here?
// if so, then the Entity union is missing at least one entry from EntityTypes

请注意,所有 type 的并集属性 Entity写为Entity['type'] ,一个indexed access type它回答了这个问题:“如果我有一个 e 类型的值 Entity 并用 'type' 类型的值对其进行索引...即 e.type ,我读取的值的类型是什么?”自 Entity是一个工会,该工会的每个成员都有不同的 type属性(property),你最终会得到这些的联合。

另请注意 EntityCompletenessCheck是一个虚拟类型,除了错误检查之外我们不用于任何其他用途,因此它的名称和值并不重要。我把它称为EntityCompletenessCheck并分配void到它,但你可以将其称为 Foo并分配 "bar"如果你愿意的话。

无论如何,上面的代码没有错误。但是如果我要向EntityTypes添加一个新值Entity 中没有相应的条目,像这样:

enum EntityTypes {
  EntityA = 'A',
  EntityB = 'B',
  EntityC = 'C',
  EntityD = 'D',
  EntityE = 'E', // <-- new enum member
}
type Entity = EntityA | EntityB | EntityC | EntityD // <-- oops, forgot

然后你会得到一个错误吗:

type EntityCompletenessCheck<T extends Entity['type']
  = EntityTypes> = void;
//  ~~~~~~~~~~~ <-- ERROR! Type 'EntityTypes' does not satisfy the constraint 
// 'EntityTypes.EntityA | EntityTypes.EntityB | EntityTypes.EntityC | EntityTypes.EntityD'

这表明Entity['type']未捕获全部 EntityTypes枚举成员,并且您需要修复某些内容(例如,将 EntityE 接口(interface)添加到 Entity 联合,或从 EntityE 枚举中删除 EntityTypes)。


所以就这样吧。该代码大部分是可读的,如果开发人员不记得维护 Entity受歧视的工会,那么将会出现一个错误,希望能提醒他们,或者你。


Playground link to code

关于typescript - 从枚举接口(interface)上的 prop 解析类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70355215/

相关文章:

typescript - 在泛型函数中将参数回调签名推断为元组?

typescript - 定义 TypeScript 回调类型并提供默认回调值

Typescript:区分条件类型中的泛型联合

typescript - 如何在 vue 组合 api 组件中使用 jest 进行单元测试?

android - 未捕获的语法错误 : Unexpected identifier when async/await is present

node.js - 30 天后自动删除 MongoDB 文档

typescript - 是否可以将 typeof 与泛型函数一起使用?

typescript - 将函数返回值定义为读取对象中路径的类型

TypeScript Express 错误函数

javascript - 如何在 typescript 中突出显示选项卡菜单中的事件选项卡