所以我有一个大型网络应用程序。它解决了很多实体问题。 例如:
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
,因为它看起来像是一个容易出错的代码块,原因如下:
- 由于我们是一个致力于此代码库的大型团队,我不想依赖其他程序员记住维护此类型
- 这里的可读性很糟糕
我花了很多时间试图找到一个优雅的解决方案,但似乎没有取得什么成果。
@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
,其中 defaults至EntityTypes
,这是所有 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
受歧视的工会,那么将会出现一个错误,希望能提醒他们,或者你。
关于typescript - 从枚举接口(interface)上的 prop 解析类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70355215/