michael
michael

Reputation: 4547

typescript: any difference between number[] and [number,number]?

I'm just learning Typescript and trying to understand best practices for types and interfaces. I am playing with an example using GPS coordinates and want to know if one approach is better than another.

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

I suppose the bigger question is if there is a value in typing an array of fixed size & type. Because Typescript does not allow me to easily test if a value is of a certain type on runtime, correct? (i.e. unserialized from a JSON string)

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

Upvotes: 7

Views: 14340

Answers (6)

shaedrich
shaedrich

Reputation: 5735

Maybe I'm a bit late but best practise would probably be using 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;
        }
    });

Upvotes: 0

Daniel Earwicker
Daniel Earwicker

Reputation: 116714

Further to @Paleo's answer on the difference between arrays and tuples, in a forthcoming version of TS the relationship between tuples, arrays and indexed interfaces has been formalised, so we can now safely say precisely what a tuple is in terms of other features. (I don't think this has changed anything, but before tuples were handled as a special case by the compiler).

A tuple [T0, T1, T2] will now be exactly equivalent to:

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

Note how the array interface is supported, but its element type is the union of all the tuple member types. This makes tuples considerably looser than in other languages; they are (clearly) not pure tuples but are a semi-restricted array. It also means that the array methods (such as pop, push etc) must all be taken into account by the type checker. Hence:

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

That is a type error, because the pop method of [1, 2, "3"] returns number|string, where as a.pop is supposed to only return number.

The inclusion of Array in the interface may cause problems for other future work, and there is an open issue suggesting a breaking change to remove it and so make tuples much stricter, i.e. fixed length and without pop etc.

Upvotes: 1

Paleo
Paleo

Reputation: 23712

It seems the tuple [number, number] doesn't restrict the number of values. It is like number[] but with a minimum of two values:

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]

The interface with numeric keys is like [number, number] for the two first values, but it doesn't ensure the type of next values:

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

Upvotes: 1

Lizzy
Lizzy

Reputation: 2173

Typescript doesn't allow runtime type checking of interfaces. Instead, you can use your 3rd method by defining Coordinate as a class.

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

Also, typescript is intended to enforce static typing to otherwise dynamically typed javascript. But your method 2 doesn't necessarily limit the size of the array to 2 elements and gps2[10] is still a valid argument (although it's value would be of type any).

For readability, I would recommend using the method mentioned above in my answer.

Upvotes: 2

DaSch
DaSch

Reputation: 941

Well first, the point why you check doesn't work is, that interfaces only exist in TypeScript. In the moment it is executed as JavaScript there is no interface Coordinate anymore. If you would like to use something like this you need to create a class and call it with new.

class Coordinate extends Array<number> {}

let gps3 = new Coordinate();

gps3.push(1);
gps3.push(3);
let check = gps3 instanceof Coordinate;

I personally always use Array<number>

  1. This notation is also used in other languages
  2. When using other generics like for example Observables you also have to use it
  3. It's clearer and you don't have no such kind of confusion with number[] [number] and [number, number] which I think is basically the same. Okay as the other answers correctly shows it's not the same.

Upvotes: 1

Nitzan Tomer
Nitzan Tomer

Reputation: 164297

In your example gps1 is an array of numbers and gps2 is a tuple of two numbers:

Tuple types allow you to express an array where the type of a fixed number of elements is known, but need not be the same.

The reason that gps3 instanceof Coordinate does not work is that Coordinate is an interface and not an actual runtime type.
The compiler user that interface to type check your code, but it does not get translated into javascript.

You can create a type guard for that:

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]);
}

Upvotes: 6

Related Questions