首页 > 解决方案 > 对象[] | object[] 类型缺少 'find(),foreach()' 的调用签名

问题描述

我有两个具有以下接口的数组变量:

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[])=>值是S,thisArg?:any):S;(谓词:(值:IShop,索引:数字,obj:IShop[])=>布尔,thisArg?:任何):IShop;} | { ...; }' 没有兼容的调用签名。

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

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

标签: javascriptarraysangulartypescripttypes

解决方案


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

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

这意味着为了调用它,回调应该接受一个同时是 and 的项目,IShop并且IHotel还会同时产生一个IShopand IHotel

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

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

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

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

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

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

游乐场链接

引入一个超类型

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

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

游乐场链接

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

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

作为Linda Paiste评论中的一个绝妙建议

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

这会给你这个:

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

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

游乐场链接

这是两个同质阵列和混合阵列之间的交集。

此声明解析为(IShop[] & (IShop | IHotel)[]) | (IHotel[] & (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

游乐场链接

附录:用allRegions初始化澄清

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

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


推荐阅读