Improvements for ReadonlyArray and readonly tuples
TypeScript 3.4 makes it a little bit easier to use read-only array-like types.
A new syntax for ReadonlyArray
The ReadonlyArray
type describes Array
s that can only be read from.Any variable with a reference to a ReadonlyArray
can’t add, remove, or replace any elements of the array.
function foo(arr: ReadonlyArray<string>) {
arr.slice(); // okay
arr.push("hello!"); // error!
}
While it’s good practice to use ReadonlyArray
over Array
when no mutation is intended, it’s often been a pain given that arrays have a nicer syntax.Specifically, number[]
is a shorthand version of Array<number>
, just as Date[]
is a shorthand for Array<Date>
.
TypeScript 3.4 introduces a new syntax for ReadonlyArray
using a new readonly
modifier for array types.
function foo(arr: readonly string[]) {
arr.slice(); // okay
arr.push("hello!"); // error!
}
readonly tuples
TypeScript 3.4 also introduces new support for readonly
tuples.We can prefix any tuple type with the readonly
keyword to make it a readonly
tuple, much like we now can with array shorthand syntax.As you might expect, unlike ordinary tuples whose slots could be written to, readonly
tuples only permit reading from those positions.
function foo(pair: readonly [string, string]) {
console.log(pair[0]); // okay
pair[1] = "hello!"; // error
}
The same way that ordinary tuples are types that extend from Array - a tuple with elements of type T 1 , T 2 , … T n extends from Array< T 1 | T 2 | … T n > - readonly tuples are types that extend from ReadonlyArray . So a readonly tuple with elements T 1 , T 2 , … T n extends from ReadonlyArray< T 1 | T 2 | … T n > . |
readonly mapped type modifiers and readonly arrays
In earlier versions of TypeScript, we generalized mapped types to operate differently on array-like types.This meant that a mapped type like Boxify
could work on arrays and tuples alike.
interface Box<T> { value: T }
type Boxify<T> = {
[K in keyof T]: Box<T[K]>
}
// { a: Box<string>, b: Box<number> }
type A = Boxify<{ a: string, b: number }>;
// Array<Box<number>>
type B = Boxify<number[]>;
// [Box<string>, Box<number>]
type C = Boxify<[string, boolean]>;
Unfortunately, mapped types like the Readonly
utility type were effectively no-ops on array and tuple types.
// lib.d.ts
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
// How code acted *before* TypeScript 3.4
// { readonly a: string, readonly b: number }
type A = Readonly<{ a: string, b: number }>;
// number[]
type B = Readonly<number[]>;
// [string, boolean]
type C = Readonly<[string, boolean]>;
In TypeScript 3.4, the readonly
modifier in a mapped type will automatically convert array-like types to their corresponding readonly
counterparts.
// How code acts now *with* TypeScript 3.4
// { readonly a: string, readonly b: number }
type A = Readonly<{ a: string, b: number }>;
// readonly number[]
type B = Readonly<number[]>;
// readonly [string, boolean]
type C = Readonly<[string, boolean]>;
Similarly, you could write a utility type like Writable
mapped type that strips away readonly
-ness, and that would convert readonly
array containers back to their mutable equivalents.
type Writable<T> = {
-readonly [K in keyof T]: T[K]
}
// { a: string, b: number }
type A = Writable<{
readonly a: string;
readonly b: number
}>;
// number[]
type B = Writable<readonly number[]>;
// [string, boolean]
type C = Writable<readonly [string, boolean]>;
Caveats
Despite its appearance, the readonly
type modifier can only be used for syntax on array types and tuple types.It is not a general-purpose type operator.
let err1: readonly Set<number>; // error!
let err2: readonly Array<boolean>; // error!
let okay: readonly boolean[]; // works fine
You can see more details in the pull request.