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

Sza*_*aci 6 arrays callback typescript 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);
Run Code Online (Sandbox Code Playgroud)

我想我明白为什么会发生这种情况,但我的问题是如何在 map 函数上进行严格的输入,以便在传入字符串时出现此错误:

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

为什么没有抛出错误是因为我正在给 map() 一个回调。这个 map() 需要这种类型的回调:

'(value: string | number, index: number, array: (string | number)[]) => Element'
Run Code Online (Sandbox Code Playgroud)

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

'(item: number, i: number) => Element'
Run Code Online (Sandbox Code Playgroud)

因此,与其检查 'number' 是否包含 'string | number', TypeScript 检查是否 '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));
Run Code Online (Sandbox Code Playgroud)

'IMix' 类型不存在属性 'cash'。
'ICard' 类型不存在属性 'cash'。

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

interface IMix {
  cash?: number;
  cardtype?: string;
  cardnumber?: string;
}
Run Code Online (Sandbox Code Playgroud)

Tit*_*mir 5

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); 
Run Code Online (Sandbox Code Playgroud)

所以打字稿不允许这种行为的原因是不能证明它对任何带有签名的函数都是正确的,(val: number, i: 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" }
];

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));
Run Code Online (Sandbox Code Playgroud)

请参阅此处的解释UnionToIntersection(并赞成使这成为可能的答案:))

T extends any ? Partial<T> : never将参加联盟各成员(因为条件类型分布在工会),使之Partial意,现在所有的领域都是可选的。然后我们使用UnionToIntersection将部分的并集转换为部分的交集。结果是一个类型,其中包含联合的每个成员中的所有字段,但所有字段都标记为部分。