javascript - 对象[] | 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
吗?
解决方案
数组联合的方法签名合并问题
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
还会同时产生一个IShop
and 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 IShop
or array of IHotel
) 导致方法签名合并是不可调用的,我们可以将类型更改为(IShop | IHotel)[]
(a array of IShop
and IHotel
items)。这有点不正确,因为您没有混合数组。但是,在实践中几乎没有区别。您仍然需要知道每个项目是什么,因此这与拥有任一类型的数组非常相似。
它的工作原理是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
引入一个超类型
与上述非常相似,但您需要更改类型:
interface IDataItem {
name: string,
id: number,
}
export interface IShop extends DataItem {
type: string,
}
export interface IHotel extends IDataItem {
rooms: number,
}
这是将共享属性提取到接口,然后两者兼而有之IShop
并IHotel
对其进行扩展。这样你就可以更直接地说,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 : [])
是多余的。你只分配一个空数组allRegions
是hotels
一个空数组(shops
也是)。由于空数组在任何情况下都是空数组,因此您可以将其缩短为const allRegions = shops.length > 0 ? shops : hotels
- 如果hotels
为空,则无论如何您都是空数组。这是我在上面的代码示例中使用的,因为它使代码更易于阅读。
只要您不打算就地改变数组,它就具有完全相同的效果。这可能会修改错误的数组。
推荐阅读
- prolog - Prolog: How to convert hexadecimal value to integer in Prolog
- sql - 为什么我在更新级联上使用时出现错误?
- php - Is IonCube 7.2 backwards compatible with IonCube 5.6 PHP code?
- azure - 在使用 Azure 以太坊 PoA 解决方案模板设置的以太坊网络上获取初始以太币
- sql - 在 sql server 2008 中将列名和值转换为行名和行值
- javascript - Jquery: each button click, in the same class must to replace a content of div stored in vars depends on attr value
- c# - Get all Process handles in C#
- python - How do I patch object imported by another python file?
- javascript - Javascript 中的递归不起作用(CodeWars 问题)
- ajax - 如何在 JSP 中停止 Ajax 上的重定向