Reputation: 4547
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
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
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
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
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
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>
number[]
[number]
and [number, number]
which I think is basically the same.Upvotes: 1
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