【问题标题】:Why type inference is lost when using Array.isArray?为什么在使用 Array.isArray 时类型推断会丢失?
【发布时间】:2019-11-16 00:21:57
【问题描述】:

我只是想知道为什么智能感知的类型推断会在 Array.isArray 条件中丢失。

考虑以下 sn-p:

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 中测试。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    它是一个已知的issue,带有ReadonlyArray 和类型保护函数Array.isArray。您还可以找到可能的修复方法here

    换句话说,为什么原始类型没有从它的声明中保留,而是更改为获取 isArray 签名?

    部分原因是,Array&lt;any&gt; 实际上是ReadonlyArray&lt;any&gt;子类型

    type IsArraySubtypeOfROArray = Array<any> extends ReadonlyArray<any> ? true : false // true
    type IsROArraySubtypeOfArray = ReadonlyArray<any> extends Array<any> ? true : false // false
    

    正如 Maciej 在他的回答中所说,没有必要进行检查,因为您已经可以确定在上述情况下有一个数组。因此,让我们假设属性 tags 具有类型 T | ReadonlyArray&lt;T&gt; 以使其更有趣。

    自带ArrayConstructor接口

    interface ArrayConstructor {
      ...
      isArray(arg: any): arg is any[];
    }
    

    如果Array.isArray(tags) 返回true,编译器会将tags 中的T | ReadonlyArray&lt;T&gt; 类型与isArray 返回类型中的any[] 类型进行比较。 TReadonlyArray&lt;T&gt; 都不是 any[] 的子类型。因此,如果any[]TReadonlyArray&lt;T&gt; 的子类型,编译器就会反过来看。与 ReadonlyArray&lt;T&gt; 的情况一样,控制流分析现在解析为 any[] 作为可能的最窄类型。

    这是一个playground,带有一个正确键入的示例。

    【讨论】:

    • 我把最佳答案移到了这里。如前所述,我的真实代码比 sn-p 复杂得多,而且我不能依赖简单的声明,因为数据来自另一个来源(也就是说,该字段甚至可能是未定义的)。当我有一个需要数组的可选参数时,我通常使用 isArray。
    【解决方案2】:

    问题出在

    Array.isArray(tags)
    

    如果通过此类型保护,则表明您使用any[]。如果您检查某个东西是否是一个数组,这对我们来说是很容易理解的。这个守卫不检查元素,而且很难检查,因为数组可以为空。使用Array.isArray 的标准情况是您不知道自己得到了什么(例如联合类型,其中值可以是也可以不是数组),并且您希望确保它是一个数组。接下来是检查数组的内容。

    您的示例非常不寻常,因为不需要检查,因为您已静态设置将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

    【讨论】:

    • 我的例子是一段较大的代码。 “标签”字段是接口的一部分,但实际对象可能来自“任何地方”,因此不能保证它将是一个数组。但是,如果它是一个数组,它是由 that T 组成的。无论如何,谢谢你:我错过了这个视角。
    • 对不起,我觉得ford04的回答更详尽,所以我给了他奖励。再次感谢您的明确解释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-16
    • 1970-01-01
    • 1970-01-01
    • 2015-06-20
    • 2011-03-19
    相关资源
    最近更新 更多