类型保护可以应用于接口的成员吗



我有以下代码:

enum Fruit {
Apple,
Banana,
}
interface FruitWrapper {
fruit: Fruit;
//...
}
function IOnlyLikeApples(o: FruitWrapper & { fruit: Fruit.Apple }) { ... }

和一个switch作为我的类型保护

const o: ISomeInterface = getFruit();
switch(o.fruit) {
case Fruit.Apple:
IOnlyLikeApples(o);
break;
}

我在将o传递给IOnlyLikeApples时遇到以下错误:

Argument of type 'FruitWrapper' is not assignable to parameter of type 'FruitWrapper & { fruit: Fruit.Apple; }'.
Type 'FruitWrapper' is not assignable to type '{ fruit: Fruit.Apple; }'.
Types of property 'fruit' are incompatible.
Type 'Fruit' is not assignable to type 'Fruit.Apple'.

TS为什么抱怨这件事?有一种观点认为,即使引用o是常数,其成员也不是,但o.fruit没有被重新分配。即使我使用interface FruitWrapper { readonly fruit: Fruit; },TS仍然拒绝此代码。

我能想到的唯一解决方案是以下内容,

const o: ISomeInterface = getFruit();
const f = o.fruit;
switch(f) {
case Fruit.Apple:
IOnlyLikeApples({ ...o,  fruit: f });
break;
}

这是丑陋和恶心的(在这一点上,不值得编译器支持来帮助IOnlyLikeApples的调用方(。

CCD_ 8与CCD_。

我有点工作,但不是那么漂亮:

const getFruit = () => {
if (Math.random() > 0.5) {
return {
fruit: Fruit.Banana,
misc: 'asd'
// the as const is needed here. Unfortunate.
} as const
} else {
return {
fruit: Fruit.Apple,
misc: 'asd'
// and here. Unfortunate again.
} as const
}
}
const o = getFruit();
switch(o.fruit) {
case Fruit.Apple:
IOnlyLikeApples(o);
break;
case Fruit.Banana:
IOnlyLikeApples(o);
}

游乐场链接

我以前在尝试使用开关作为类型保护时遇到过这个问题。这是因为typescript在切换开始时将类型分配给对象o,并且从不重新计算它。如果您查看case Fruit.Apple分支中的oo.fruit的值,您会发现TS知道o.fruit的类型是Fruit.Apple,但仍然认为o的类型为FruitWrapper,并且没有向其添加任何其他内容。

有很多方法可以解决这个问题。最简单、最草率的是简单地断言你所知道的:IOnlyLikeApples(o as FruitWrapper & { fruit: Fruit.Apple });

你必须使用switch语句吗?如果您可以在if内部使用类型保护来重写它,那么您就不会有这个问题。

type Apple = FruitWrapper & { fruit: Fruit.Apple };
const isApple = (o: FruitWrapper): o is Apple => {
return o.fruit === Fruit.Apple;
}
const handleFruit = ( o: FruitWrapper ) => {
if ( isApple(o) ) {
IOnlyLikeApples(o);
} else {
/*...*/
}
}

另一种选择是将水果作为并集类型,这取决于你的数据,可能有意义,也可能没有意义。switch在接口内的指定方面做得不够,但它能够处理联合内的区分。这很好:

type Apple = FruitWrapper & { fruit: Fruit.Apple };
type Banana = FruitWrapper & { fruit: Fruit.Banana };
type FruitUnion = Apple | Banana;
const handleFruit = ( o: FruitUnion ) => {
switch(o.fruit) {
case Fruit.Apple:
IOnlyLikeApples(o);
break;
case Fruit.Banana:
/* ... */
break;
}
}

游乐场链接

最新更新