Tuples in TypeScript
Tomohiro Yoshida
Posted on March 19, 2023
Do you know what Tuple is? If you are a JavaScript developer, I guess you are unfamiliar with this data type because there is no Tuple in JavaScript.
Tuple is a more strict version of Array, which can store only a static number of elements and types at each index.
Let me clarify the difference between Tuple and a simple Array and explain how to use it.
Type Declaration
Union-Type Array
When you want to use a simple union-type array, the syntax is as follows:
type UnionArrayType = (string | number)[];
let unionArray1: UnionArrayType;
unionArray1 = ["hello world", 1]; // OK
let unionArray2: UnionArrayType;
unionArray2 = [2, "hello world"]; // OK
let unionArray3: UnionArrayType;
unionArray3 = [3, "hello", "world"]; // OK
let unionArray4: UnionArrayType;
unionArray4 = [4, "hello", "world", true];
// Type 'boolean' is not assignable to type 'string | number'.
The array of UnionArrayType
can accept any number of members as long as the types of elements are string or number.
Tuple
The syntax for Tuple is as follows:
type MyTupleType = [string, number];
let myTuple1: MyTupleType;
myTuple1 = ["hello world", 1]; // OK
let myTuple2: MyTupleType;
myTuple2 = [2, "hello world"];
// Type 'number' is not assignable to type 'string'.
// Type 'string' is not assignable to type 'number'.
let myTuple3: MyTupleType;
myTuple3 = ["hello world", 1, 2];
// Type '[string, number, number]' is not assignable to type 'MyTupleType'.
// Source has 3 element(s) but target allows only 2.
As you can see above, MyTupleType cannot accept the array whose order is “number and string”, and the array whose number of members is more than or less than two.
What is the benefit of Tuples?
If the order of members in an array affects your program, Tuple Type will bring benefits to you. For instance, you may pass arguments to a function by using the … rest parameters syntax.
Here is a simple example.
type PersonType = [string, number];
const checkAgeToDrink = (name: string, age: number) => {
const legalAgeToDrink = 20;
const displayName = name[0].toUpperCase() + name.slice(1);
const canDrink = age >= legalAgeToDrink;
if(canDrink) {
console.log(displayName + " can drink!");
} else {
console.log(displayName + " has to wait " + (legalAgeToDrink - age) + " years until they can drink.");
}
};
const person1: PersonType = ["john", 10];
checkAgeToDrink(...person1);
// OK [LOG]: "John has to wait 10 years until they can drink."
const person2 = ["mary", 21];
checkAgeToDrink(...person2);
// A spread argument must either have a tuple type or be passed to a rest parameter.
const person3: [number, string] = [30, "mike"];
checkAgeToDrink(...person3);
// Argument of type 'number' is not assignable to parameter of type 'string'.
In the example above, only person1
can be passed as … rest parameters because person1
variable guarantees the order of members’ type.
Speaking of person2
, you will see the error message from the TypeScript type checker though it will run at runtime without any issues because the type of this variable, (string | number)[]
, does not guarantee the order of its members. TypeScript type checker notices a potential bug in advance and lets developers know what might be wrong.
When it comes to person3
's case, it must be wrong because the order of the members’ type is wrong.
Tuple inferences
When you see the previous example code, you might wonder what is wrong with person2
.
Let's look into the details of the difference between person1
and person2
.
const person1: PersonType = ["john", 10];
// const person1: PersonType
// type PersonType = [string, number]
const person2 = ["mary", 21];
// const person2: (string | number)[]
As you can see, the type of person2
is automatically inferred as Union-Type Array while person1
is declared with the type annotation to make it the Tuple
type. It means you need to explicitly provide a type annotation when you want to use the Tuple type.
Alternative way to use Tuple, Const asserted tuples
It may be annoying for some developers to write type annotations for Tuples since it is just an extra syntax.
If you feel like that, there is another option. That is the so-called “const assertion”.
By providing a const assertion operator after a value, you can make a tuple as follows:
const checkAgeToDrink = (name: string, age: number) => {
......
};
const person2 = ["mary", 21] as const;
checkAgeToDrink(...person2); // OK
Please keep in your mind that the tuples created by a const assertion are not completely the same as a tuple created with an explicit type annotation because the const assertion gives a read-only feature to a value.
const person2 = ["mary", 21] as const;
// const person2: readonly ["mary", 21]
person2[0] = "leo";
// Cannot assign to '0' because it is a read-only property.
Summary
- There is a specific type of array that is called a “Tuple” in TypeScript
- Tuples are more strict and can store only a static number of elements and types at each index, while Union-type arrays are more flexible
- Tuple type cannot accept arrays whose order of types is different and the number of members is different.
- If the order of members in an array affects your program, Tuple Type will bring benefits to developers
- If you want to use Tuples, there are two ways of doing it.
- Explicit annotations
- Const assertions
Posted on March 19, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.