【问题标题】:typescript: any difference between number[] and [number,number]?打字稿:数字[]和[数字,数字]之间有什么区别?
【发布时间】:2019-03-18 15:58:36
【问题描述】:

我只是在学习 Typescript,并试图了解类型和接口的最佳实践。我正在玩一个使用 GPS 坐标的示例,并想知道一种方法是否比另一种更好。

let gps1 : number[];
let gps2 : [number, number]
interface Coordinate {
   0: number,
   1: number
}
let gps3 : Coordinate;

我想更大的问题是输入固定大小和类型的数组是否有价值。因为 Typescript 不允许我在运行时轻松测试某个值是否属于某种类型,对吗? (即从 JSON 字符串中反序列化)

gps3 = [1,3]
let check = gps3 instanceof Coordinate;  // doesn't work

【问题讨论】:

标签: typescript


【解决方案1】:

在您的示例中,gps1 是一个数字数组,gps2 是两个数字的 a tuple

元组类型允许你表达一个数组,其中的类型是固定的 元素的数量是已知的,但不必相同。

gps3 instanceof Coordinate 不起作用的原因是Coordinate 是一个接口而不是实际的运行时类型。
输入接口的编译器用户检查你的代码,但它没有被翻译成 javascript。

您可以为此创建一个type guard

interface Coordinate {
   0: number,
   1: number
}

function isCoordinate(obj: any): obj is Coordinate {
    return obj instanceof Array && obj.length === 2 && typeof obj[0] === "number" && typeof obj[1] === "number";
}

let a;

if (isCoordinate(a)) {
    console.log(a[0]);
}

【讨论】:

    【解决方案2】:

    Typescript 不允许对接口进行运行时类型检查。相反,您可以通过将Coordinate 定义为一个类来使用您的第三种方法。

    class Coordinate {
        longitude: number;
        latitude: number;
    
        constructor(long, lat) {
            this.longitude = long;
            this.latitude = lat;
        }
    }
    let gps3 = new Coordinate(1,2);
    
    if(gps3 instanceof Coordinate) // true
    

    此外,typescript 旨在将静态类型强制用于其他动态类型的 javascript。但是您的方法 2 不一定将数组的大小限制为 2 个元素,gps2[10] 仍然是一个有效的参数(尽管它的值将是any 类型)。

    为了可读性,我建议使用上面我的回答中提到的方法。

    【讨论】:

    • 使用此方法,您可以使用gps3.latitude 按名称访问纬度和经度,与gps[1] 相比,它提高了可读性。
    • 这个方法对我来说很有意义,而且它确实使代码更容易阅读。但它需要更多的工作,并且在某些情况下可能是多余的。 “类型保护”可能是可读性的下一个最佳选择。仍在尝试了解最佳实践应该是什么。
    • instanceof 是类型守卫之一
    【解决方案3】:

    首先,您检查不起作用的原因是,接口仅存在于 TypeScript 中。在它作为 JavaScript 执行的那一刻,不再有接口 Coordinate。如果你想使用这样的东西,你需要创建一个类并用 new 调用它。

    class Coordinate extends Array<number> {}
    
    let gps3 = new Coordinate();
    
    gps3.push(1);
    gps3.push(3);
    let check = gps3 instanceof Coordinate;
    

    我个人一直使用Array&lt;number&gt;

    1. 此表示法也用于其他语言
    2. 使用其他泛型(例如 Observables)时,您还必须使用它
    3. 它更清楚了,你不会对number[] [number][number, number] 产生这种混淆,我认为它们基本上是相同的。 好的,因为其他答案正确地表明它不一样.

    【讨论】:

      【解决方案4】:

      元组[number, number] 似乎没有限制值的数量。它类似于number[],但至少有两个值:

      let gps2 : [number, number];
      gps2 = [1, 2, 3]; // ok
      gps2 = [1, 2, 'abc']; // Error: Type [number, number, string] is not assignable to type [number, number]
      gps2 = [1]; // Error: Type [number] is not assignable to type [number, number]
      

      数字键的接口对于前两个值类似于[number, number],但不保证下一个值的类型:

      interface Coordinate {
          0: number,
          1: number
      }
      let gps3 : Coordinate;
      gps3 = [1, 2]; // ok
      gps3 = [1, 'abc']; // Error: Type [number, string] is not assignable to type 'Coordinate'
      gps3 = [1, 2, 'abc']; // ok
      

      【讨论】:

      • 这是结构类型。 [1, 2, 'abc'][number, number] 完美兼容。 (在 OO 术语中,它是一个子类)。
      • @DanielEarwicker [1, 2, 'abc'] 与带有 TypeScript 的 [number, number] 不兼容(在 1.8 中尝试过)。我写了这个答案是因为它是对以下问题的回答:“number[] 和 [number,number] 之间有什么区别?”
      • 是的,你的回答很清楚。我只是认为这是 TS 中一个奇怪的不一致。对于具有 2 个元素的元组类型的实例 xx[2](带有数字文字索引)应该是类型错误。但是,TS 允许元组包含/访问更多元素,只要它们与指定元素类型的 union 兼容。这意味着您可以将元组[number, string, number | string] 分配给[number, string] 类型的变量!此外,如果你有一个 x 类型为 [number, string] 的变量,你可以得到 x[2] ,它将是 number|string 类型。
      • x[y](其中y:number)应该具有number|string 类型是有道理的,因为y 的值在编译时是未知的。现在他们在 2.0 中有了数字字面量类型,也许 y 应该被限制为 0|1 类型,尽管现在这需要一个 --strictTuples 标志。
      • 我在 TS repo 中找到了一些信息,这些信息更清楚地说明了元组的去向,因此将其作为单独的答案。
      【解决方案5】:

      除了@Paleo 对数组和元组之间区别的回答之外,in a forthcoming version of TS 元组、数组和索引接口之间的关系已被形式化,因此我们现在可以安全地准确地说出元组在其他特性方面是什么。 (我认为这没有改变任何东西,但在元组被编译器作为特例处理之前)。

      元组 [T0, T1, T2] 现在将完全等同于:

      interface Tuple<T0, T1, T2> extends Array<T0 | T1 | T2> {
          0: T0,
          1: T1,
          2: T2
      }
      

      注意数组接口是如何被支持的,但是它的元素类型是所有元组成员类型的联合。这使得元组比其他语言更宽松;它们(显然)不是纯元组,而是半限制数组。这也意味着类型检查器必须将数组方法(例如poppush 等)全部考虑在内。因此:

      const a: [number, number] = [1, 2, "3"];
      

      这是一个类型错误,因为[1, 2, "3"]pop 方法返回number|string,而a.pop 应该只返回number

      在界面中包含Array 可能会导致other future work 出现问题,并且有一个open issue 建议进行重大更改以将其删除,从而使元组更加严格,即固定长度并且没有pop 等.

      【讨论】:

        【解决方案6】:

        也许我来晚了,但最佳做法可能是使用 ECMA6 Proxy

            latLngLiteral = new Proxy({},{
                set: function(obj, prop, val) {
                    //only these two properties can be set
                    if(['lng','lat'].indexOf(prop) == -1) {
                        throw new ReferenceError('Key must be "lat" or "lng"!');
                    }
        
                    //the dec format only accepts numbers
                    if(isNaN(val)) {
                        throw new TypeError('Value must be numeric');
                    }
        
                    //latitude is in range between 0 and 90
                    if(prop == 'lat'  && !(-90 < val && val < 90)) {
                        throw new RangeError('Position is out of range!');
                    }
                    //longitude is in range between 0 and 180
                    else if(prop == 'lng' && !(-180 < val && val < 180)) {
                        throw new RangeError('Position is out of range!');
                    }
        
                    obj[prop] = val;
        
                    return true;
                }
            });
        

        【讨论】:

          猜你喜欢
          • 2022-01-05
          • 2016-07-08
          • 1970-01-01
          • 1970-01-01
          • 2013-09-19
          • 2021-09-07
          • 1970-01-01
          相关资源
          最近更新 更多