TypeScript Quirks: Arrays vs Tuples
So, in TypeScript we can define an array as being a list of things. This could be a single type of thing, or multiple possible things. Additionally, as briefly covered in a previous post, we can define a tuple… but it’s not necessarily what you’d think.
An array is defined in one of two ways…
type MyArray = string[];
…for simple arrays of a single type, and…
type MyArray = Array<string | number>;
…for arrays that may contain multiple types.
Similarly defining array in the following ways will infer the same types as we’ve defined above.
const numberArray = [1, 2, 3, 4, 5];
const mixedArray = ['a', 2, 'c'];
The issue with these is that we can’t say for sure what type an item in an array is going to be when accessing it. Not without further checks anyway. E.g.
const item = mixedArray[2];
This item still has the type string | number
, so we have to make additional checks such as typeof
, or for more complex types; type gaurds, as discussed in this post.
There is a way, however, that we can define the exact items that should be in an array. TypeScript calls these tuples, although they’re not exactly the tuples you may be used to.
All we have to do is use the following syntax when defining our type:
type FixedArray = [string, number, string];
Awesome, right? Now we can use this to type the mixedArray
example from above and we’ll get exactly the types we’d expect.
const mixedArray: FixedArray = ['a', 2, 'c'];
const first = mixedArray[0]; // string
const second = mixedArray[1]; // number
Here, though, is where I explain the weird bit…
This is a fixed length array, with an explicit set of values, so we shouldn’t really be able to access values outside of this length… but we can… and what’s weirder is the types you get when you do.
const fourth = mixedArray[3]; // string | number
Now we’ve got another string | number
type.
Okay, so maybe we can set an item outside of the range and it will have the correct type? Should we even be able to do that? We’ve defined an exact set of values for this tuple.
mixedArray[3] = 4;
const fourth = mixedArray[3]; // string | number
Nope, we can happily set a value outside of this range, but it will still be a mix of the types defined for the know part of the array.
“This isn’t what I signed up for when I decided to use tuples” I hear you saying. “The whole reason we wanted to use a tuple was to retrieve the exact type of the item.” Luckily, we can, for the length that we’ve defined, and that’s all that tuples are really for. They will ensure that the items we’ve defined are the types that we want, and will prevent us from putting a string in place of a number, etc, but they don’t guard values outside of this range… and here’s why…
Javascript doesn’t have tuples. They just don’t exist. TypeScript has to use javascript’s arrays to represent a tuple, and although it can type check the area that you are accessing, it cannot guarantee that another module isn’t mutating values in your arrays.
So, there you have it; tuples - useful, if a little crazy, but hey, this is a language based on javascript, it has to have some madness.