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

Arrays 如何在TypeScript中正确键入Array.map回调?,arrays,typescript,callback,typescript-typings,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); 我想我理解为什么会发生这种情况,但我的问题是如何在映射函数上

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

function eachItem(val: number, i: number) {
  return Number(val) / 2;
}
const arr = [4, "more", 6];
arr.map(eachItem);
我想我理解为什么会发生这种情况,但我的问题是如何在映射函数上进行严格的键入,以便在传入字符串时出现以下错误:

“string”类型的参数不能分配给“number”类型的参数

之所以没有抛出错误,是因为我正在回调map()。此映射()需要此类型的回调:

'(value: string | number, index: number, array: (string | number)[]) => Element'
'(item: number, i: number) => Element'
我给出了一个这种类型的回调:

'(value: string | number, index: number, array: (string | number)[]) => Element'
'(item: number, i: number) => Element'
因此,TypeScript没有检查“number”是否包含“string | number”,而是检查“string | number”是否包含“number”,并发现它是真的。我的逻辑是我们需要一个错误,因为我传递的是只允许数字的“更多”。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”上不存在属性“cash”

我知道,现在我必须执行以下操作,但我无法在代码中表示cash和cardtype相互排斥:

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

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); 
因此,typescript不允许这种行为的原因是,无法证明它对于任何签名为
(val:number,i:number)=>string
的函数都是正确的,但它取决于函数的具体实现

现在在第二个示例中,函数可以访问字段,并且不会出现运行时错误。字段可能未定义,但这对该实现没有多大影响。我们可以做的是创建一个类型,其中union中所有可能的字段都是可选的,这实际上是查看数组中的项的另一种方式(只是不是typescript默认查看它的方式)。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));
interface-IMoney{
现金:数字;
}
接口ICard{
卡片类型:字符串;
卡号:字符串;
}
IMix型=IMoney | ICard;
常量菜单=[
{现金:566},
{卡片类型:“信用卡”,卡号:“111111111”}
];
输入UnionToIntersection=(U扩展任何?(k:U)=>void:never)扩展((k:inferi)=>void)?I:从来没有
类型PartialUnion=UnionToIntersection
menu.map((项目:PartialUnion,i:number)=>(item.cash | | item.cardtype));
有关
UnionToIntersection
的解释,请参阅(并向上投票使之成为可能的答案:)

T扩展了什么?部分:从不
将接受联合中的每个成员(因为条件类型分布在联合上),并使其成为部分,这意味着所有字段现在都是可选的。然后我们使用
UnionToIntersection
将部分的并集转换为部分的交集。结果是一个类型,其中包含联合的每个成员中的所有字段,但所有字段都标记为部分字段。

您似乎希望使用一个而不是联合类型

type IMix = IMoney & ICard;

不。这将是一种必须有卡号、卡型和现金的付款方式。没有这样的事。我需要钱或者卡。据我所知,提香的回答将你的联合类型转换为交叉类型,所以我看不出有什么区别。