泛型判别联合 [英] Discriminated Union of Generic type
问题描述
我希望能够使用工会歧视 带有泛型.但是,它似乎不起作用:
I'd like to be able to use union discrimination with a generic. However, it doesn't seem to be working:
示例代码(在打字稿操场上查看):
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!
}
}
我希望打字稿能够认识到,因为我区分了通用的 item
属性,所以 genericThing
必须是 GenericThing
.
I was hoping that typescript would recognize that since I discriminated on the generic item
property, that genericThing
must be GenericThing<Foo>
.
我猜这只是不受支持?
另外,有点奇怪的是,在直接赋值之后,它 fooThing.item
失去了它的辨别力.
Also, kinda weird that after straight assignment, it fooThing.item
loses it's discrimination.
推荐答案
问题
区分联合中的类型缩小受到若干限制:
The problem
Type narrowing in discriminated unions is subject to several restrictions:
没有展开泛型
首先,如果类型是泛型,则泛型不会被解包以缩小类型:缩小需要联合才能工作.因此,例如这不起作用:
Firstly, if the type is generic, the generic will not be unwrapped to narrow a type: narrowing needs a union to work. So, for example this does not work:
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;
}
}
虽然这样做:
let func = (genericThing: GenericThing<'foo'> | GenericThing<'bar'>) => {
switch (genericThing.item) {
case 'foo':
genericThing; // now GenericThing<'foo'> !
break;
case 'bar':
genericThing; // now GenericThing<'bar'> !
break;
}
}
我怀疑解包具有联合类型参数的泛型类型会导致编译器团队无法以令人满意的方式解决的各种奇怪的极端情况.
I suspect unwrapping a generic type that has a union type argument would cause all sorts of strange corner cases that the compiler team can't resolve in a satisfactory way.
不会因嵌套属性而缩小
即使我们有类型的联合,如果我们测试嵌套属性,也不会发生缩小.一个字段类型可能会根据测试缩小,但不会缩小根对象:
Even if we have a union of types, no narrowing will occur if we test on a nested property. A field type may be narrowed based on the test, but the root object will not be narrowed:
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;
}
}
解决办法
解决方案是使用自定义类型保护.我们可以制作一个非常通用的类型保护版本,它适用于任何具有 type
字段的类型参数.不幸的是,我们不能为任何泛型类型创建它,因为它会绑定到 GenericThing
:
The solution
The solution is to use a custom type guard. We can make a pretty generic version of the type guard that would work for any type parameter that has a type
field. Unfortunately, we can't make it for any generic type since it will be tied to 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;
}
};
这篇关于泛型判别联合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!