我只是想知道为什么智能感知的类型推断在Array.isArray
条件内丢失了。
请考虑以下代码段:
type T = {
readonly name: string;
readonly descr: string;
}
interface I{
readonly tags: ReadonlyArray<T>;
}
function Z(arg: I): void{
const { tags } = arg;
if (Array.isArray(tags)) { //hovering "tags" here shows "readonly T[]"
for (let t of tags) { //hovering "tags" here shows "any[]"
}
}
}
Z({
tags:[]
})
换句话说,为什么原始类型不从其声明中保留,而是更改为获取isArray
签名?
在Visual Studio以及playground中进行了测试。
答案 0 :(得分:1)
这是已知的issue,带有ReadonlyArray
和类型保护功能Array.isArray
。您还可以找到可能的解决方法here。
换句话说,为什么原始类型不从其声明中保留,而是更改为获取isArray签名?
部分原因是Array<any>
实际上是ReadonlyArray<any>
的子类型。
type IsArraySubtypeOfROArray = Array<any> extends ReadonlyArray<any> ? true : false // true
type IsROArraySubtypeOfArray = ReadonlyArray<any> extends Array<any> ? true : false // false
正如Maciej在他的回答中所说,没有必要检查,因为您已经可以确定在上述情况下有一个数组。因此,假设属性tags
的类型为T | ReadonlyArray<T>
,使其变得更有趣。
带有内置的ArrayConstructor
界面
interface ArrayConstructor {
...
isArray(arg: any): arg is any[];
}
并给定Array.isArray(tags)
返回true
,则编译器将T | ReadonlyArray<T>
的类型tags
与any[]
的返回类型isArray
进行比较。 T
和ReadonlyArray<T>
都不是any[]
的子类型。因此,如果any[]
是T
或ReadonlyArray<T>
的子类型,则编译器会采用另一种方式。与ReadonlyArray<T>
的情况一样,控制流分析现在解析为any[]
作为可能的最窄类型。
这是一个playground,带有正确键入的示例。
答案 1 :(得分:0)
问题出在
Array.isArray(tags)
如果通过此类型保护,则说明您正在使用any[]
。如果您检查某物是否为数组,这对我们来说是可以理解的。该防护不检查元素,并且由于数组可以为空,因此很难检查。使用Array.isArray
的标准情况是您不知道所得到的结果(例如,联合类型(其中value可以是数组,也可以不是数组)),并且您想确保它是一个数组。接下来就是检查数组。
您的示例非常不常见,因为您无需进行检查,因为您已静态设置为获取T[]
作为输入。因此,对于类型系统Array.isArray(tags)
毫无意义,因为tags
是T的数组。
您可以通过自定义类型防护来解决此问题:
const isArray = <V>(a: any): a is V[] => Array.isArray(a);
// above guard set additional type information to the array element
// usage
if (isArray<T>(tags)) { //hovering "tags" here shows "readonly T[]"
tags // here tags are T[]
}
但是在您的示例中,直到输入的具体程度不如T[]
才有意义,例如,对于T | T[]
这样的输入就有意义:
interface I{
readonly tags: ReadonlyArray<T> | T; // union type
}
然后检查就有意义了,因为我们在这里分支以对T[]
和T
进行不同的处理。