您会遇到TypeScrip缺乏对correlated unions的直接支持,就像在microsoft/TypeScript#30581中所描述的那样.就像你说的,attribute.validate(attribute.value)
不起作用,因为TypeScrip没有跟踪attribute
中的identity,只跟踪了type.因此,它判断代码就像您编写attribute1.validate(attribute2.validate)
一样,因为Attribute
是union type,所以它不能确保您没有将参数从一个联合成员传递给来自另一个联合成员的函数.在TypeScrip中直接调用带有参数联合的函数联合基本上是不可能的,而编译器不会抱怨.
在这种情况下,推荐的方法被描述为microsoft/TypeScript#47109,并且涉及到重构,以使用generics,constrained是联合的constrained,而不是联合本身.其 idea 是提出一个"基"对象类型,表示 struct 中随每个联合成员而变化的部分,然后按照indexed accesses到基类型或mapped types到mapped types的形式表示操作.我们的目标是,attribute.validate()
将被视为参数与attribute.value
的类型完全相同的单个函数类型.
对于您的示例,重构可能是这样的.基本对象类型为
interface AttributeMap {
string: string,
bool: boolean;
}
然后,您的Attribute
类型可以表示为对AttributeMap
以上的映射类型的通用索引:
type Attribute<K extends keyof AttributeMap = keyof AttributeMap> =
{ [P in K]: BaseAttribute<AttributeMap[P]> & { type: P } }[K]
表格{[P in K]: F<P>}[K]
的一种类型被称为distributive object type,因为它将K
中的并集转换为F<K>
中的并集.我的意思是,如果K
是K1 | K2 | K3
,那么{[P in K]: F<P>}[K]
是F<K1> | F<K2> | F<K3>
.所以Attribute<keyof AttributeMap>
相当于你原来的Attribute
类型.由于default type argument为keyof AttributeMap
,这意味着当您提到没有类型参数的类型Attribute
时,您得到的类型等同于原始的Attribute
(允许我们保留一些原始代码不变).
但现在由于Attribute
是通用的,您可以编写Attribute<"string">
来恢复StringAttribute
,编写Attribute<"bool">
来恢复BoolAttribute
.当我们使用泛型类型参数K
时,Attribute<K>
将被视为具有非并集validate
属性和非并集value
属性的东西.
当attribute
是类型Attribute
时,您仍然不能只写attribute.validate(attribute.value)
.但是当它是泛型K
的类型Attribute<K>
时,你can就会这么做.这意味着我们应该将该代码包装在一个泛型函数中:
function validateAttribute<K extends keyof AttributeMap>(attribute: Attribute<K>) {
attribute.validate(attribute.value); // okay
}
现在,您终于可以在Union类型的attribute
上调用该函数了:
const attribute = getAttribute();
validateAttribute(attribute);
所以,你go .请注意,microsoft/TypeScript#47109中的方法不是microsoft/TypeScript#48730%类型安全的,仍然存在潜在的问题,您可能最终会欺骗编译器允许实际上不安全的代码;请参阅microsoft/TypeScript#48730.但这样做肯定要安全得多,而不是使用原始代码并使用type assertions来 suppress 错误.
Playground link to code