使用 Array.every 缩小数组类型的并集
Narrowing a union of array types using Array.every
我有一个类型为不同数组类型联合的变量。我想将其类型缩小为该联合的单个成员,这样我就可以 运行 为每种类型在其上添加适当的代码。在这里使用 Array.every
and a custom type guard 似乎是正确的方法,但是当我尝试使用此 TypeScript 时会抱怨“此表达式不可调用”。连同我不明白的解释。
这是我的最小可重现示例:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];
if (unionArr.every(isNumber)) { // <- Error
unionArr;
}
这是错误:
This expression is not callable.
Each member of the union type
'{
<S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[];
(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean;
} | {
...;
}'
has signatures, but none of those signatures are compatible with each other.
这并不妨碍我继续。我发现在我缩小其类型之前使用 type assertion 将我的数组重新转换为 unknown[]
,就像我编写 isNumberArray
自定义类型保护时我会做的那样,无需删除错误危及类型安全。
我还发现将我的 string[] | number[]
数组重铸为 (string | number)[]
可以消除错误。
然而,数组的类型似乎没有正确缩小,所以我需要在检查后使用额外的 as number[]
:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];
if ((unionArr as unknown[]).every(isNumber)) { // <- No error
unionArr; // <- Incorrectly typed as string[] | number[]
}
if ((unionArr as (string | number)[]).every(isNumber)) { // <- No error
unionArr; // <- Incrrectly typed as string[] | number[]
}
我也尝试与非数组联合进行比较,当然在这种情况下我只是直接使用自定义类型保护而不是将其与 Array.every
一起使用。在那种情况下,也没有错误并且正确缩小了类型:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const union: string | number = Math.random() > 0.5 ? 1 : '1';
if (isNumber(union)) {
union; // <- Correctly typed as number
}
因为我有安全类型断言解决方法,所以我可以继续而不需要理解它。但我仍然很困惑为什么首先会出现该错误,因为我正在尝试将类型联合缩小为该联合的单个成员。
我猜这与 TypeScript 键入 Array.every
的方式有关,除了我已经在使用的解决方法外,我可能无能为力。但是当我真的不明白哪里出了问题时,很难确定这一点。我可以在这里做些不同的事情,还是 as unknown[]
类型断言我使用了正确或最好的方法来处理这个问题?
让我们举一个更简单的例子:
type StringOrNumberFn = (a: string | number) => void
type NumberOrBoolFn = (a: number | boolean) => void
declare const justNumber: StringOrNumberFn | NumberOrBoolFn
// works
justNumber(123)
// errors
justNumber('asd')
justNumber(true)
当您尝试调用函数联合时,实际上调用的是这些成员的 交集 的函数。如果您不知道它是哪个函数,那么您只能以 both 函数支持的方式调用该函数。在这种情况下,两个函数都可以采用 number
,所以这是允许的。
如果这些函数的交集有不兼容的参数,则无法调用该函数。那么让我们建模:
type StringFn = (a: string) => void
type NumberFn = (a: number) => void
declare const fnUnion: StringFn | NumberFn
fnUnion(123) // Argument of type 'number' is not assignable to parameter of type 'never'.(2345)
fnUnion('asdf') // Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
这更接近你的问题。
字符串数组的 every
和数字数组的 every
被键入以接收不同的参数。
(value: string, index: number, array: string[]) // string[] every() args
(value: number, index: number, array: number[]) // number[] every() args
这与上面的问题本质上是一样的。
所以我认为编译器无法在该联合上调用 every
方法。
相反,我可能会为整个数组编写一个类型断言并手动遍历它。
const isNumberArray = (array: unknown[]): array is number[] => {
for (const value of array) {
if (typeof value !== 'number') return false
}
return true
}
declare const unionArr: string[] | number[]
if (isNumberArray(unionArr)) {
Math.round(unionArr[0]); // works
}
我有一个类型为不同数组类型联合的变量。我想将其类型缩小为该联合的单个成员,这样我就可以 运行 为每种类型在其上添加适当的代码。在这里使用 Array.every
and a custom type guard 似乎是正确的方法,但是当我尝试使用此 TypeScript 时会抱怨“此表达式不可调用”。连同我不明白的解释。
这是我的最小可重现示例:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];
if (unionArr.every(isNumber)) { // <- Error
unionArr;
}
这是错误:
This expression is not callable.
Each member of the union type
'{
<S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[];
(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean;
} | {
...;
}'
has signatures, but none of those signatures are compatible with each other.
这并不妨碍我继续。我发现在我缩小其类型之前使用 type assertion 将我的数组重新转换为 unknown[]
,就像我编写 isNumberArray
自定义类型保护时我会做的那样,无需删除错误危及类型安全。
我还发现将我的 string[] | number[]
数组重铸为 (string | number)[]
可以消除错误。
然而,数组的类型似乎没有正确缩小,所以我需要在检查后使用额外的 as number[]
:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];
if ((unionArr as unknown[]).every(isNumber)) { // <- No error
unionArr; // <- Incorrectly typed as string[] | number[]
}
if ((unionArr as (string | number)[]).every(isNumber)) { // <- No error
unionArr; // <- Incrrectly typed as string[] | number[]
}
我也尝试与非数组联合进行比较,当然在这种情况下我只是直接使用自定义类型保护而不是将其与 Array.every
一起使用。在那种情况下,也没有错误并且正确缩小了类型:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const union: string | number = Math.random() > 0.5 ? 1 : '1';
if (isNumber(union)) {
union; // <- Correctly typed as number
}
因为我有安全类型断言解决方法,所以我可以继续而不需要理解它。但我仍然很困惑为什么首先会出现该错误,因为我正在尝试将类型联合缩小为该联合的单个成员。
我猜这与 TypeScript 键入 Array.every
的方式有关,除了我已经在使用的解决方法外,我可能无能为力。但是当我真的不明白哪里出了问题时,很难确定这一点。我可以在这里做些不同的事情,还是 as unknown[]
类型断言我使用了正确或最好的方法来处理这个问题?
让我们举一个更简单的例子:
type StringOrNumberFn = (a: string | number) => void
type NumberOrBoolFn = (a: number | boolean) => void
declare const justNumber: StringOrNumberFn | NumberOrBoolFn
// works
justNumber(123)
// errors
justNumber('asd')
justNumber(true)
当您尝试调用函数联合时,实际上调用的是这些成员的 交集 的函数。如果您不知道它是哪个函数,那么您只能以 both 函数支持的方式调用该函数。在这种情况下,两个函数都可以采用 number
,所以这是允许的。
如果这些函数的交集有不兼容的参数,则无法调用该函数。那么让我们建模:
type StringFn = (a: string) => void
type NumberFn = (a: number) => void
declare const fnUnion: StringFn | NumberFn
fnUnion(123) // Argument of type 'number' is not assignable to parameter of type 'never'.(2345)
fnUnion('asdf') // Argument of type 'string' is not assignable to parameter of type 'never'.(2345)
这更接近你的问题。
字符串数组的 every
和数字数组的 every
被键入以接收不同的参数。
(value: string, index: number, array: string[]) // string[] every() args
(value: number, index: number, array: number[]) // number[] every() args
这与上面的问题本质上是一样的。
所以我认为编译器无法在该联合上调用 every
方法。
相反,我可能会为整个数组编写一个类型断言并手动遍历它。
const isNumberArray = (array: unknown[]): array is number[] => {
for (const value of array) {
if (typeof value !== 'number') return false
}
return true
}
declare const unionArr: string[] | number[]
if (isNumberArray(unionArr)) {
Math.round(unionArr[0]); // works
}