我正在使用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;
}
答案 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;