无法调用类型缺少呼叫签名的表达式


76

我有苹果和梨-都有一个isDecayed属性:

interface Apple {
    color: string;
    isDecayed: boolean;
}

interface Pear {
    weight: number;
    isDecayed: boolean;
}

两种类型都可以放在我的水果篮中(多次):

interface FruitBasket {
   apples: Apple[];
   pears: Pear[];
}

现在假设我的购物篮是空的:

const fruitBasket: FruitBasket = { apples: [], pears: [] };

现在我们从篮子中随机抽取一种:

const key: keyof FruitBasket = Math.random() > 0.5 ? 'apples': 'pears'; 
const fruits = fruitBasket[key];

当然,没有人喜欢腐烂的水果,因此我们只选择新鲜的水果:

const freshFruits = fruits.filter((fruit) => !fruit.isDecayed);

不幸的是打字稿告诉我:

无法调用类型缺少调用签名的表达式。类型'((callbackfn:(值:苹果,索引:数字,数组:苹果[])=>任何,thisArg ?:任何)=>苹果[])| ...'没有兼容的呼叫签名。

这是怎么回事-仅仅是Typescript不喜欢新鲜水果还是这是Typescript错误?

您可以在官方Typescript Repl中自己尝试。



1
有什么理由不能Fruit使用该isDecayed属性扩展通用接口,然后将水果声明为类型Fruit[]
Gerrit0

奇怪...没有错误,如果您将显式设置keystringieconst key: string = Math.random() > 0.5 ? 'apples': 'pears';
shusson 17/02/23

之所以会出现^,是因为没有显式的索引签名,将使用返回类型来any规避此问题。
shusson

Answers:


87

TypeScript支持结构化类型化(也称为鸭子类型化),这意味着类型在共享相同成员时是兼容的。您的问题是Apple并且Pear不共享所有成员,这意味着它们不兼容。但是,它们与仅具有该isDecayed: boolean成员的另一种类型兼容。由于结构打字,你不”需要继承ApplePear从这样的接口。

分配这种兼容类型的方法有多种:

在变量声明期间分配类型

该语句隐式键入为Apple[] | Pear[]

const fruits = fruitBasket[key];

您可以简单地在变量声明中显式使用兼容类型:

const fruits: { isDecayed: boolean }[] = fruitBasket[key];

为了获得更高的可重用性,您还可以先定义类型,然后在声明中使用它(请注意,无需更改AppleandPear接口):

type Fruit = { isDecayed: boolean };
const fruits: Fruit[] = fruitBasket[key];

强制转换为兼容类型

给定解决方案的问题在于它会更改fruits变量的类型。这可能不是您想要的。为避免这种情况,您可以在操作前将数组缩小为兼容类型,然后将其设置回与以下类型相同的类型fruits

const fruits: fruitBasket[key];
const freshFruits = (fruits as { isDecayed: boolean }[]).filter(fruit => !fruit.isDecayed) as typeof fruits;

或具有可重用Fruit类型:

type Fruit = { isDecayed: boolean };
const fruits: fruitBasket[key];
const freshFruits = (fruits as Fruit[]).filter(fruit => !fruit.isDecayed) as typeof fruits;

此解决方案的优点是fruitsfreshFruits均为type Apple[] | Pear[]


6

如在最初由@peter链接的github问题中所述:

const freshFruits = (fruits as (Apple | Pear)[]).filter((fruit: (Apple | Pear)) => !fruit.isDecayed);

2
我没有混合使用的苹果和梨,而是混合使用的苹果或梨。
Erdem

1
我使用Typescript的原因相同:构造代码时,我或第三人称可以更好地了解代码在做什么以及代码在处理什么。苹果和梨的混合排列已经不再是那种排列了。那么,为什么要将其定义为混合数组呢?
Erdem

如果我错了,请纠正我,但是关于该数组将包含Apple或Pears的声明不是虚假的,只是没有描述全部事实。
shusson

1
你绝对正确。但是,如果我有机会更详细地描述它,那么我想使用该方法,这就是我不明白为什么Typescript将更详细的描述作为错误的原因。
Erdem

4

也许创建一个Fruit提供isDecayed的共享接口。fruits现在是类型,Fruit[]因此类型可以是显式的。像这样:

interface Fruit {
    isDecayed: boolean;
}

interface Apple extends Fruit {
    color: string;
}

interface Pear extends Fruit {
    weight: number;
}

interface FruitBasket {
    apples: Apple[];
    pears: Pear[];
}


const fruitBasket: FruitBasket = { apples: [], pears: [] };
const key: keyof FruitBasket = Math.random() > 0.5 ? 'apples': 'pears'; 
const fruits: Fruit[] = fruitBasket[key];

const freshFruits = fruits.filter((fruit) => !fruit.isDecayed);

谢谢-这是我在此期间使用的解决方法,但对我来说,它仍然感觉更像是hack,而不是真正的解决方案。这是唯一的方法吗?
Erdem

这事儿常常发生。您FruitBasket也可能只是Fruits的数组
Frambot

-2

我有一个数字问题,即JS库。解决方法是使用此命令再次安装类型:

npm install --save @types/numeral
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.