Read-Only Array and Tuple Types in TypeScript
TypeScript 3.4 added a bit of syntactic sugar to the language that makes it easier to work with read-only array and tuple types. We can now use the readonly
modifier to create read-only array types (such as readonly string[]
) or read-only tuple types (such as readonly [number, number]
).
#Read-Only Array Types in TypeScript
Let's assume we've defined the following intersperse
function:
function intersperse<T>(array: T[], separator: T): T[] {
const newArray: T[] = [];
for (let i = 0; i < array.length; i++) {
if (i !== 0) {
newArray.push(separator);
}
newArray.push(array[i]);
}
return newArray;
}
The intersperse
function accepts an array of elements of some type T
and a separator value of the same type T
. It returns a new array of elements with the separator value interspersed in between each of the elements. In a way, the intersperse
function is similar to the Array.prototype.join()
method, except that it returns an array of the same type instead of a string.
Here are some usage examples of our intersperse
function:
intersperse(["a", "b", "c"], "x");
// ["a", "x", "b", "x", "c"]
intersperse(["a", "b"], "x");
// ["a", "x", "b"]
intersperse(["a"], 0);
// ["a"]
intersperse([], 0);
// []
Let's now create an array that is annotated to be of type ReadonlyArray<string>
, a read-only array type:
const values: ReadonlyArray<string> = ["a", "b", "c"];
This means that we don't intend for this array to be mutated. TypeScript's type checker will produce an error if we try to write to the array or call mutating array methods such as push()
, pop()
, or splice()
:
values[0] = "x"; // Type error
values.push("x"); // Type error
values.pop(); // Type error
values.splice(1, 1); // Type error
Alternatively, we could've used the new readonly
modifier to type our values
array as a read-only array:
const values: readonly string[] = ["a", "b", "c"];
ReadonlyArray<string>
and readonly string[]
represent the same type; you can pick whichever syntax you prefer. I like readonly T[]
because it's more concise and closer to T[]
, but your mileage may vary. It's just a matter of preference.
What happens if we now try to pass values
to intersperse
?
const valuesWithSeparator = intersperse(values, "x");
TypeScript gives us another type error!
Argument of type 'readonly string[]' is not assignable to parameter of type 'string[]'.
The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
The type checker points out that the mutable array type string[]
cannot be assigned to the read-only array type readonly string[]
. Here, the potential problem is that our intersperse
function could call mutating methods on the array
parameter. That would violate the intended read-only behavior of the values
array.
We can make the type error go away by typing the array
parameter as a read-only array. By doing that, we're indicating that our intersperse
function is not going to mutate the array
array:
function intersperse<T>(array: readonly T[], separator: T): T[] {
const newArray: T[] = [];
for (let i = 0; i < array.length; i++) {
if (i !== 0) {
newArray.push(separator);
}
newArray.push(array[i]);
}
return newArray;
}
const values: readonly string[] = ["a", "b", "c"];
const valuesWithSeparator = intersperse(values, "x");
If you're writing a pure function that accepts an array as a parameter, I would recommend that you annotate that array parameter to be read-only. That way, your function can be called with mutable and read-only arrays alike. In addition, TypeScript will help you prevent accidental mutation of those parameters within the function.
If you want to experiment with read-only array types and play around with the above type annotations, I've prepared this TypeScript playground for you.
#Read-Only Tuple Types in TypeScript
Similar to read-only array types, TypeScript lets us create read-only tuple types using the readonly
modifier:
const point: readonly [number, number] = [0, 0];
Any attempt to mutate a value of a read-only tuple type will result in a type error:
point[0] = 1; // Type error
point.push(0); // Type error
point.pop(); // Type error
point.splice(1, 1); // Type error
For tuple types, there's no equivalent of the ReadonlyArray
type. You'll have to rely on the readonly
modifier to make a tuple type read-only.
Again, if you want to play around with tuple types and the readonly
modifier, feel free to use this TypeScript playground.
This article and 44 others are part of the TypeScript Evolution series. Have a look!