NSj*_*nas 11 discriminated-union typescript
我希望能够使用通用的工会歧视.但是,它似乎没有工作:
示例代码(在打字稿操场上查看):
interface Foo{
type: 'foo';
fooProp: string
}
interface Bar{
type: 'bar'
barProp: number
}
interface GenericThing<T> {
item: T;
}
let func = (genericThing: GenericThing<Foo | Bar>) => {
if (genericThing.item.type === 'foo') {
genericThing.item.fooProp; // this works, but type of genericThing is still GenericThing<Foo | Bar>
let fooThing = genericThing;
fooThing.item.fooProp; //error!
}
}
Run Code Online (Sandbox Code Playgroud)
我希望打字稿会认识到,因为我对通用item属性genericThing有所区别,所以必须如此GenericThing<Foo>.
我猜这只是不支持?
而且,有点奇怪的是,在直接分配后,它fooThing.item会失去它的歧视.
Tit*_*mir 11
受歧视的工会中的类型缩小受到若干限制:
没有解开泛型
首先,如果类型是通用的,那么泛型将不会被解包以缩小类型,缩小需要联合才能工作.所以例如这不起作用:
let func = (genericThing: GenericThing<'foo' | 'bar'>) => {
switch (genericThing.item) {
case 'foo':
genericThing; // still GenericThing<'foo' | 'bar'>
break;
case 'bar':
genericThing; // still GenericThing<'foo' | 'bar'>
break;
}
}
Run Code Online (Sandbox Code Playgroud)
虽然这样做:
let func = (genericThing: GenericThing<'foo'> | GenericThing<'bar'>) => {
switch (genericThing.item) {
case 'foo':
genericThing; // now GenericThing<'foo'> !
break;
case 'bar':
genericThing; // now GenericThing<'bar'> !
break;
}
}
Run Code Online (Sandbox Code Playgroud)
怀疑展开具有联合类型参数的泛型类型会导致编译器团队无法以令人满意的方式解决的所有奇怪的极端情况.
嵌套属性没有缩小范围
即使我们有一个类型联合,如果我们测试嵌套属性也不会发生缩小.可以根据测试缩小字段类型,但不会缩小根对象:
let func = (genericThing: GenericThing<{ type: 'foo' }> | GenericThing<{ type: 'bar' }>) => {
switch (genericThing.item.type) {
case 'foo':
genericThing; // still GenericThing<{ type: 'foo' }> | GenericThing<{ type: 'bar' }>)
genericThing.item // but this is { type: 'foo' } !
break;
case 'bar':
genericThing; // still GenericThing<{ type: 'foo' }> | GenericThing<{ type: 'bar' }>)
genericThing.item // but this is { type: 'bar' } !
break;
}
}
Run Code Online (Sandbox Code Playgroud)
解决方案是使用自定义类型防护.我们可以创建一个非常通用的类型保护版本,适用于任何具有type字段的类型参数.不幸的是,我们无法为任何通用类型创建它,它将绑定到GenericThing:
function isOfType<T extends { type: any }, TValue extends string>(
genericThing: GenericThing<T>,
type: TValue
): genericThing is GenericThing<Extract<T, { type: TValue }>> {
return genericThing.item.type === type;
}
let func = (genericThing: GenericThing<Foo | Bar>) => {
if (isOfType(genericThing, "foo")) {
genericThing.item.fooProp;
let fooThing = genericThing;
fooThing.item.fooProp;
}
};
Run Code Online (Sandbox Code Playgroud)
正如@Titian 所解释的,当你真正需要的是:
GenericThing<'foo'> | GenericThing<'bar'>
Run Code Online (Sandbox Code Playgroud)
但你有一些定义为:
GenericThing<'foo' | 'bar'>
Run Code Online (Sandbox Code Playgroud)
显然,如果您只有两个这样的选择,您可以自己扩展它,但这当然是不可扩展的。
假设我有一个带有节点的递归树。这是一个简化:
// different types of nodes
export type NodeType = 'section' | 'page' | 'text' | 'image' | ....;
// node with children
export type OutlineNode<T extends NodeType> = AllowedOutlineNodeTypes> =
{
type: T,
data: NodeDataType[T], // definition not shown
children: OutlineNode<NodeType>[]
}
Run Code Online (Sandbox Code Playgroud)
由 表示的类型OutlineNode<...>需要是可区分的 union,这是因为它们的type: T属性。
假设我们有一个节点的实例,并且我们迭代子节点:
const node: OutlineNode<'page'> = ....;
node.children.forEach(child =>
{
// check a property that is unique for each possible child type
if (child.type == 'section')
{
// we want child.data to be NodeDataType['section']
// it isn't!
}
})
Run Code Online (Sandbox Code Playgroud)
显然,在这种情况下,我不想定义具有所有可能的节点类型的子节点。
另一种方法是“分解”NodeType我们定义儿童的地方。不幸的是,我找不到一种方法来使其通用,因为我无法提取类型名称。
相反,您可以执行以下操作:
// create type to take 'section' | 'image' and return OutlineNode<'section'> | OutlineNode<'image'>
type ConvertToUnion<T> = T[keyof T];
type OutlineNodeTypeUnion<T extends NodeType> = ConvertToUnion<{ [key in T]: OutlineNode<key> }>;
Run Code Online (Sandbox Code Playgroud)
那么定义children修改为:
children: OutlineNodeTypeUnion<NodeType>[]
Run Code Online (Sandbox Code Playgroud)
现在,当您迭代子级时,它是所有可能性的扩展定义,并且受歧视的联合类型保护会自行启动。
为什么不直接使用打字机呢?1)你真的不需要。2)如果使用像角度模板这样的东西,你不希望对你的打字保护程序进行无数次调用。这种方式实际上允许自动缩小类型范围,*ngIf但不幸的是不允许ngSwitch
| 归档时间: |
|
| 查看次数: |
2168 次 |
| 最近记录: |