代码之家  ›  专栏  ›  技术社区  ›  Metabolic

object[]| object[]类型缺少“find(),foreach()”的调用签名

  •  2
  • Metabolic  · 技术社区  · 4 年前

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

    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,值:IShop,索引: 数字,obj:IShop[])=>值是S,this Arg?:任何):S;(谓词: (值:IShop,索引:number,对象:IShop[])=>boolean,this参数?: 任何):IShop;}|“{…;}”没有兼容的呼叫签名。

    在编译过程中,其他Array方法也会发生同样的情况,尽管代码运行良好,我知道问题的含义,但我不清楚为什么Typscript无法识别Array。

    关于打字检查 allRegion ,我明白了 IShop[] | IHotel[] 这两个都是数组,其数据类型是否有问题 所有地区 ?

    1 回复  |  直到 4 年前
        1
  •  3
  •   VLAZ Sohail Arif    4 年前

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

    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
    

    这意味着,为了调用它,回调函数应该接受一个同时满足以下条件的项目 IShop IHotel 同时也将产生 IShop IH酒店 同时。

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

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

    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[]| I酒店[] (一系列 IShop 或数组 IH酒店 )导致方法签名合并不可调用,我们可以将类型更改为 (IShop | IHotel)[] (一系列 IShop IH酒店 项目)。这有点不正确,因为您没有混合数组。然而,在实践中几乎没有区别。你仍然需要知道每个项目是什么,所以这与拥有任何一种类型的数组非常相似。

    它之所以有效,是因为 IShop | IHotel 将允许您使用两个接口之间的共享属性。在这种情况下, name id 因此,TypeScript将允许如下调用 allRegions.find(r => 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,   
    }
    

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

    const allRegions: IDataItem[]  = shops.length > 0 ? shops : hotels;
    
    allRegions.find(r => r.name === 'name'); //allowed
    

    Playground Link

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

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

    As a brilliant suggestion in a comment 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
    

    Playground Link

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

    本声明决定 (IShop[] & (IShop | IHotel)[]) | (IHotel[] & (IShop | IHotel)[]) 这是一个联盟

    • 同质 IShop 阵列与混合阵列相交 IShop |酒店 阵列
    • 同质 IH酒店 阵列与混合阵列相交 IShop |酒店 阵列

    最棒的是,它的行为与 IShop[]| I酒店[] -你不能混合。然而,与此同时,该类型将确保方法声明合并正确工作。这意味着您可以对只有一种类型的项但没有混合的数组进行正确的类型检查:

    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

    附录:澄清 所有地区 初始化

    线 const allRegions = shops.length > 0 ? shops : (hotels.length > 0 ? hotels : []) 是多余的。您只能将空数组分配给 所有地区 hotels 是一个空数组(和 shops 也)。由于空数组在任何一种情况下都是空数组,因此可以将其缩短为 const allRegions = shops.length > 0 ? shops : hotels -如果 酒店 如果是空的,那么你仍然是一个空数组。这就是我在上面的代码示例中使用的,因为它使代码更容易阅读。

    只要你不打算在原地对数组进行突变,它就会产生完全相同的效果。这可能会修改错误的数组。

        2
  •  -2
  •   B45i    4 年前

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

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

    Fiddle

    推荐文章