问题
受歧视的联合中的类型缩小受到以下几个限制:
不展开泛型
首先,如果类型是泛型的,泛型将不会被展开以缩小类型范围:缩小范围需要联合才能工作。例如,这不起作用:
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;
}
}
我怀疑展开一个具有union类型参数的泛型类型会导致编译器团队无法以令人满意的方式解决的各种奇怪的角落情况。
不按嵌套属性缩小
即使我们有类型的并集,如果我们在嵌套属性上测试,也不会发生收缩。可以根据测试缩小字段类型,但不会缩小根对象:
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
:
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;
}
};