【问题标题】:Cannot invoke an expression whose type lacks a call signature无法调用其类型缺少调用签名的表达式
【发布时间】:2017-07-14 14:55:12
【问题描述】:

我有苹果和梨 - 两者都有 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);

不幸的是 Typescript 告诉我:

无法调用类型缺少调用签名的表达式。输入 '((callbackfn: (value: Apple, index: number, array: Apple[]) => any, thisArg?: any) => Apple[]) | ...' 没有兼容的调用签名。

这里出了什么问题 - 仅仅是 Typescript 不喜欢新鲜水果还是这是一个 Typescript 错误?

你可以在官方Typescript Repl自己试试。

【问题讨论】:

  • 有什么理由不能用isDecayed 属性扩展一个通用的Fruit 接口,然后将fruits 声明为Fruit[] 类型?
  • 奇怪...如果您将key 显式设置为stringconst key: string = Math.random() > 0.5 ? 'apples': 'pears'; 没有错误
  • ^ 发生是因为没有明确的索引签名,返回类型将是 any 这可以绕过问题。

标签: javascript typescript types


【解决方案1】:

我在 JS 库中遇到了同样的问题。解决方法是使用以下命令再次安装类型:

npm install --save @types/numeral

【讨论】:

    【解决方案2】:

    TypeScript 支持结构类型(也称为鸭子类型),这意味着 types are compatible when they share the same members。您的问题是 ApplePear 不共享所有成员,这意味着它们不兼容。但是,它们与仅具有 isDecayed: boolean 成员的另一种类型兼容。由于结构类型,您不需要从这样的接口继承ApplePear

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

    在变量声明期间分配类型

    此语句隐式键入Apple[] | Pear[]

    const fruits = fruitBasket[key];
    

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

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

    为了增加可重用性,您也可以先定义类型,然后在声明中使用它(注意ApplePear 接口不需要更改):

    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 的类型均为Apple[] | Pear[]

    【讨论】:

      【解决方案3】:

      正如 @peter 在 cmets 中最初链接的 github issue 中所述:

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

      【讨论】:

      • 我没有苹果和梨的混合数组,而是苹果数组或梨数组。
      • 我使用 Typescript 的相同原因:为了构建我的代码,我或第三方可以更好地了解代码在做什么以及代码正在处理什么。苹果和梨的混合阵列不会出现在那个阵列中。那我为什么要把它定义为混合数组呢?
      • 如果我错了,请纠正我,但数组将包含 Apples 或 Pears 的声明不是错误的,它只是没有描述全部事实。
      • 你完全正确。但是,如果我有机会更详细地描述它,我想使用该方法,这就是我不明白 Typescript 将更详细的方法作为错误处理的原因。
      【解决方案4】:

      也许创建一个提供 isDecayed 的共享Fruit 接口。 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,而不是真正的解决方案。这是唯一的方法吗?
      • 这种情况经常发生。您的 FruitBasket 也可能只是 Fruits 的数组
      猜你喜欢
      • 2017-12-19
      • 2020-02-07
      • 1970-01-01
      • 2019-01-05
      • 2018-05-09
      • 2023-03-20
      • 2017-09-01
      • 2021-02-06
      • 2017-03-17
      相关资源
      最近更新 更多