【问题标题】:object[] | object[] type lacks a call signature for 'find(),foreach()'对象[] | object[] 类型缺少 'find(),foreach()' 的调用签名
【发布时间】:2021-02-12 10:00:30
【问题描述】:

我有两个数组变量,接口如下:

export interface IShop {
  name: string,
  id:   number,
  type: string,   
}

export interface IHotel {
  name: string,
  id:   number,
  rooms: number,   
}

我的打字稿代码如下:

let shops: IShop[];
let hotels: IHotel[];
//these variables then gets assigned respective data from an API matching the interfaces

const allRegions = shops.length > 0 ? shops : (hotels.length > 0 ? hotels : []);

allRegions.find(r => r.name === 'name');

在最后一行,我收到错误消息:

无法调用类型缺少调用签名的表达式。类型 '{ (谓词: (this: void, value: IShop, index: number, obj: IShop[]) => value is S, thisArg?: any): S; (谓词: (value: IShop, index: number, obj: IShop[]) => boolean, thisArg?: 任何):Ishop; } | { ...; }' 没有兼容的调用签名。

编译期间其他 Array 方法也会发生同样的情况,尽管代码可以正常工作并且我知道问题的含义,但我不清楚为什么 Typscript 无法识别 Array。

在检查allRegion 时,我得到IShop[] | IHotel[] 这两个显然都是数组,allRegion 的数据类型有什么问题吗?

【问题讨论】:

  • 推断的数据类型很棘手。你为什么不直接输入并结束呢?
  • 我猜你可以做const region = shops.length > 0 ? shops.find(r => r.name === 'name') : (hotels.length > 0 ? hotels.find(r => r.name === 'name') : undefined);。无需指定allRegions 作为中介。不过,我会使用if/else,因为我不喜欢条件运算符,尤其是嵌套多个。
  • 那我猜你可以简化为const allRegions: (IShop | IHotel)[] = shops.length > 0 ? shops : hotels;你失去了一些类型签名的准确性,但至少它是可用的。
  • "那么我需要在每个位置检查数组是否为空。" 这与您当前的解决方案有何不同?目前,如果shops 数组为空且hotels 数组为空,那么您将...分配一个空数组allRegions。由于您hotels 数组为空时分配一个空数组,因此它实际上与分配空hotels 数组相同。除非你打算做突变。但是,如果不是,则没有真正的区别-空数组最后是一个空数组。而你目前拥有它。

标签: javascript arrays angular typescript types


【解决方案1】:

将显式输入添加到allRegions,您会收到此错误,因为您在条件末尾传递的 ht 空数组没有任何类型。

const allRegions: IShop[] |  IHotel[] | any[]  = shops.length > 0 ? shops : (hotels.length > 0 ? hotels : []);

Fiddle

【讨论】:

  • any[] 是一个非常糟糕的主意。让 TS 为这些使用合并声明只是一个讨厌的技巧。没有必要添加它 - 它不准确,它可能会让人失望。更不用说把编译器扔掉了——现在allRegions.find(r => r.foobar === 'name'); 是合法的使用方法。我看不到丢弃代码的类型安全性和清晰度的价值。您不妨将整个内容投射到any[],因为这是目前有效的签名。
  • 它仍然是一个数组。 IShop[]IHotel[] 仍然是正确的。此外,问题不在于空数组 - TS 正确推断类型为IShop[] | IHotel[],因此没有真正的理由添加“空数组类型”。问题是 TS 将合并 both 数组的方法签名,这会导致签名不可调用,因为它是不可满足的 - 它需要与 both 一起工作IShop + IShop[]IHotel + IHotel[] 同时存在,这是不可能存在的。
【解决方案2】:

数组联合的方法签名合并问题

TypeScript 抱怨的原因是因为 IShop[] | IHotel[] 类型会合并所有方法签名。特别是签名:

Array<IShop>.find(
    predicate: (
        value: IShop, 
        index: number, 
        obj: IShop[]
    ) => unknown, thisArg?: any
): IShop | undefined

Array<IHotel>.find(
    predicate: (
        value: IHotel, 
        index: number, 
        obj: IHotel[]
    ) => unknown, thisArg?: any
): IHotel | undefined

实际上变成类似于:

Array<IShop & IHotel>.find(
    predicate: (
        value: IShop & IHotel, 
        index: number,
        obj: (IShop & IHotel)[]
    ) => unknown, thisArg?: any
): IShop & IHotel | undefined

这意味着为了调用它,回调应该同时接受IShopIHotel 的项目,并且同时产生IShopIHotel

这实际上是不可能的,因此编译器得出结论,由于类型签名不可满足,它也是不可调用的。

这是方法签名合并方式的一个弱点。这是合并签名的正确方法,但对于许多用例,结果类型不是您实际需要的,方法调用也不是不可满足的。它更有限满足它但绝对不是不可能:

let shops = [{name: "shop1", id: 1, type: "supermarket"}];
let hotels = [{name: "hotel1", id: 2, rooms: 42}];

// see addendum
const allRegions = shops.length > 0 ? shops : hotels;

const result = allRegions.find(r => r.name === 'shop1');

console.log(result);

问题在于,这适用于更本地化的情况,而不是更一般的情况,即调用方法的任何变体。

绕过它的方法是使用显式类型,这将允许您保持类型安全,但您必须稍微覆盖编译器的决定。

可能的解决方案

从数组联合变为联合类型数组

由于IShop[] | IHotel[]IShop 数组或IHotel 数组)导致无法调用的方法签名合并,我们可以将类型更改为(IShop | IHotel)[]IShopIHotel 项的数组)。这有点不正确,因为您没有混合数组。但是,在实践中几乎没有区别。您仍然需要知道每个项目是什么,因此这与拥有任一类型的数组非常相似。

使它起作用的是IShop | IHotel 将允许您使用两个接口之间的共享属性。在这种情况下,nameid。因此,TypeScript 将允许像 allRegions.find(r =&gt; r.name === 'name') 这样的调用。

const allRegions: (IShop | IHotel)[]  = shops.length > 0 ? shops : hotels;

allRegions.find(r => r.name === 'name'); //allowed

Playground Link

引入一个超类型

与上述非常相似,但您需要更改类型:

interface IDataItem {
  name: string,
  id:   number,
}

export interface IShop extends DataItem {
  type: string,   
}

export interface IHotel extends IDataItem {
  rooms: number,   
}

这是将共享属性提取到接口,然后IShopIHotel 扩展它。这样你就可以更直接地说allRegions 将包含超类型。结果与联合类型IShop | IHotel 基本相同,但更加明确。

const allRegions: IDataItem[]  = shops.length > 0 ? shops : hotels;

allRegions.find(r => r.name === 'name'); //allowed

Playground Link

如果您的数据实际上是相关的,则最好在您的类型中表示它。类型联合不传达有关关系的信息。但是,这仍然需要您能够更改类型。如果这不可能,那么类型联合是更好的选择。

创建一个新的联合,确保数组方法可用

作为a brilliant suggestion in a comment 来自Linda Paiste

可以声明const allRegions: (IShop[] | IHotel[]) &amp; (IShop | IHotel)[],这样我们就可以获得联合签名,而不会失去数组元素属于同一类型的限制。

这会给你这个:

const allRegions: (IShop[] | IHotel[]) & (IShop | IHotel)[] = shops.length > 0 ? shops : hotels;

allRegions.find(r => r.name === 'name'); //allowed

Playground Link

这是两个同构数组和混合数组的交集。

此声明解析为(IShop[] &amp; (IShop | IHotel)[]) | (IHotel[] &amp; (IShop | IHotel)[]),它是

  • 同质IShop 数组与混合IShop | IHotel 数组相交
  • 同质IHotel 数组与混合IShop | IHotel 数组相交

精彩之处在于它的行为与IShop[] | IHotel[] 完全相同 - 你不能混合使用。但是,与此同时,类型将确保方法声明合并正常工作。这意味着您可以对仅包含一种类型的项目但未混合的数组进行正确的类型检查:

declare let shops: IShop[];
declare let hotels: IHotel[];
//mixed array
declare let mixed: (IShop | IHotel)[];
//homogenous array of either type
declare let improved: (IShop[] | IHotel[]) & (IShop | IHotel)[];

//something that takes a homogenous array
declare function foo(x: IShop[] | IHotel[]): void;

foo(shops);    //ok
foo(hotels);   //ok
foo(mixed);    //error
foo(improved); //ok

Playground Link

附录:用allRegions初始化澄清

const allRegions = shops.length &gt; 0 ? shops : (hotels.length &gt; 0 ? hotels : []) 这一行是多余的。你只分配一个空数组给allRegionshotels 是一个空数组(和shops 也是)。由于空数组在任何情况下都是空数组,因此您可以将其缩短为 const allRegions = shops.length &gt; 0 ? shops : hotels - 如果 hotels 为空,则无论如何您都是空数组。这是我在上面的代码示例中使用的,因为它使代码更易于阅读。

只要您不打算就地改变数组,它就会产生完全相同的效果。这可能会修改错误的数组。

【讨论】:

  • 这是一个很好的答案。如果我可以建议一个微小的改进/澄清 RE:联合类型,可以声明 const allRegions: (IShop[] | IHotel[]) &amp; (IShop | IHotel)[] 以便我们获得联合签名而不会失去数组元素属于同一类型的限制。
  • 这是一个很好的答案。如果我可以建议一个微小的改进/澄清 RE:联合类型,可以声明 const allRegions: (IShop[] | IHotel[]) &amp; (IShop | IHotel)[] 以便我们获得联合签名而不会失去数组元素属于同一类型的限制。
  • @LindaPaiste 这是太棒了!我从没想过使用这个声明,但你是对的 - 它非常有意义。
猜你喜欢
  • 1970-01-01
  • 2018-11-12
  • 2019-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-01
  • 2017-12-19
  • 2021-02-06
相关资源
最近更新 更多