【问题标题】:Resolve type from prop on interface that is enum从 enum 接口上的 prop 解析类型
【发布时间】:2022-01-18 03:20:04
【问题描述】:

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

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 }>

【问题讨论】:

  • this approach 是否满足您的需求?您维护一个像 type Entity = EntityA | EntityB | EntityC | EntityD 这样的工会,您可以在需要时向其中添加新的工会成员。如果这对你有用,我可以写一个答案;如果没有,请edit您的问题以显示不满意的用例。
  • @jcalz ,按要求编辑了帖子
  • TypeScript 无法自行“发现”您的实体接口(无法迭代已知接口,而且您甚至不希望有人可以在某处添加新的 interface Whoopsie {type: EntityTypes.EntityB, randomThing: string} 并破坏您的代码; 你需要告诉它它们是哪些。像type Entity = EntityA | EntityB | EntityC | EntityD 这样的东西可能是最轻量级的方法(特别是与你的原始版本相比)。如果你愿意进行重大重构,比如this,你可以它更自动。
  • 如果最后的重构对你有用,那么我可以写出来。如果没有,并且您需要在范围内手动创建接口EntityAEntityB,那么您还必须手动将它们放入包中以供编译器查找。让我知道您想看到哪种答案。在最坏的情况下,我只会回答我最初的建议,这是对您当前代码的改进,以及为什么它不能变得更好的警告。
  • @jcalz 有什么办法可以强制执行 EntityTypeResolver 以便通过的联合将包含所有枚举键?

标签: typescript typescript-generics


【解决方案1】:

您的EntityTypesResolver&lt;T&gt; 类型函数根据为T 传入的EntityTypes 成员从一组已知接口中选择一个接口。目前,它通过一系列conditional types 来实现这一点,这很有效,但正如你所说,它的可读性不是很高。

将这些接口转换为discriminated union 并根据type 判别属性定义EntityTypesResolverExtract 该联合的一个或多个成员会更加自然和可读。看起来像这样:

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

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


不幸的是,编译器无法知道EntityTypesResolver 应该咨询哪一组接口,除非你告诉它,这是无法避免的。所以有人将不得不维护Entity 类型,确保它包含一个对应于EntityTypes 枚举的每个成员的成员。例如,无法要求编译器扫描它知道的所有接口并选择具有正确排序的type 属性的接口。

可以做的一件事(感谢您的建议)是编写一些代码,当且仅当 Entity 联合缺少来自其成员。一种方法是定义一个虚拟generic type,其类型参数为constrainedEntity 中所有type 属性的并集,其中defaultsEntityTypes,这是所有@987654345 的并集@枚举值。泛型参数默认值必须扩展它们的约束,因此这行代码用于检查 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

请注意,Entity 中所有type 属性的并集写为Entity['type']indexed access type 回答了以下问题:“如果我有一个e 类型为Entity 和索引以'type' 类型的值进入它...即e.type,我读取的值的类型是什么?”由于 Entity 是一个联合,并且该联合的每个成员都有不同的 type 属性,因此您最终会得到这些联合。

还请注意,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 接口添加到Entity 联合,或删除EntityE来自EntityTypes 枚举)。


所以你去。代码大部分是可读的,如果开发人员不记得维护 Entity 可区分联合,那么将会出现一个错误,希望能提醒他们或您。


Playground link to code

【讨论】:

  • 我仍然无法理解 Entity['type'] 解析的内容。你能说清楚吗?
  • 我编辑了答案;请参阅开头的段落:“注意联合”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-22
  • 1970-01-01
  • 2022-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多