如何在TypeScript中正确键入Array.map回调?

时间:2018-08-01 09:12:58

标签: arrays typescript callback typescript-typings

我正在使用React和TypeScript。当我使用map()遍历数组时,似乎并不总是检查类型。在下面的示例中,我将字符串“ more”传递到eachItem函数中,其中应为数字。但是没有抛出错误:

function eachItem(val: number, i: number) {
  return Number(val) / 2;
}
const arr = [4, "more", 6];
arr.map(eachItem);

我想我理解为什么会发生这种情况,但是我的问题是如何对地图函数进行严格的键入,以便在传递字符串时出现此错误:

  

“字符串”类型的参数不能分配给“数字”类型的参数

为什么不会引发错误,是因为我给map()提供了回调。此map()期望使用以下类型的回调:

'(value: string | number, index: number, array: (string | number)[]) => Element'

我正在提供这种类型的回调:

'(item: number, i: number) => Element'

因此,无需检查'number'是否包含'string |。数字”,TypeScript检查是否“字符串|数字”包含“数字”,并且发现它为真。我的逻辑说我们需要一个错误,因为我要传递“更多”,其中只允许数字。 TypeScript的逻辑说没有错误,我传递了一个允许数字的函数,其中允许了允许字符串和数字的函数。 这似乎不是什么大问题,但是当您的类型是联合体时,原本不应该的错误就会出现。这是一个示例,其结果是错误:

interface IMoney {
  cash: number;
}

interface ICard {
  cardtype: string;
  cardnumber: string;
}

type IMix = IMoney | ICard;

const menu = [
  {cash: 566},
  {cardtype: "credit", cardnumber: "111111111111111111"}
];

menu.map((item: IMix, i: number) => (item.cash || item.cardtype));
  

类型“ IMix”上不存在属性“现金”。
  属性“现金”在类型“ ICard”上不存在。

我知道,现在我必须执行以下操作,但是然后我无法在代码中表示现金和卡类型彼此排斥:

interface IMix {
  cash?: number;
  cardtype?: string;
  cardnumber?: string;
}

2 个答案:

答案 0 :(得分:1)

在第一个示例中,Typescript是正确的关于错误的信息。考虑我们是否强制编译器接受修改后的函数:

function eachItem(val: number, i: number) {
    return val.toExponential(3) // only numbers will have toExponential
}
const arr = [4, "more", 6];

// eachItem will get called with numbers and string. the strings will cause a runtime error
// runtime error val.toExponential is not a function
arr.map(eachItem as any); 

所以打字稿不允许这种行为的原因是,对于任何带有签名(val: number, i: number) => string的函数,它都不能被证明是正确的,而是取决于函数的具体实现。

现在在第二个示例中,该函数可以访问字段,并且不会出现运行时错误。字段可能是未定义的,但对于此实现而言并没有太大关系。我们可以做的是创建一个类型,在该类型中,联合中所有可能的字段都是可选的,实际上,这是查看数组中各项的另一种方式(默认情况下不是打字稿查看方式)。 Typescript可以使我们通过这样的参数传递函数,因为它是安全的,因为所有字段都是可选的,因此无论如何使用该函数之前都需要检查该函数。

interface IMoney {
    cash: number;
}

interface ICard {
    cardtype: string;
    cardnumber: string;
}

type IMix = IMoney | ICard;

const menu = [
    { cash: 566 },
    { cardtype: "credit", cardnumber: "111111111111111111" }
];

type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type PartialUnion<T> = UnionToIntersection<T extends any ? Partial<T> : never>

menu.map((item: PartialUnion<IMix>, i: number) => (item.cash || item.cardtype));

请参见here,以获取UnionToIntersection的解释(并支持使之成为可能的答案:))

T extends any ? Partial<T> : never将接受联合中的每个成员(因为条件类型分布在联合上)并使其成为Partial,这意味着所有字段现在都是可选的。然后,我们使用UnionToIntersection将部分的并集转换为部分的交集。结果是一个类型,该类型具有联合的每个成员中的所有字段,但所有字段都标记为“ partial”。

答案 1 :(得分:0)

似乎您希望使用intersection type而不是联合类型。

type IMix = IMoney & ICard;