Массивоподобные readonly типы, ReadonlyArray, ReadonlyMap, ReadonlySet¶
Чем меньше шансов случайного изменения значений, определенных в объектных типах, тем больше программа защищена от ошибок во время выполнения. Очередным шагом в этом направлении стали неизменяемые массивоподобные типы ReadonlyArray<T>
, ReadonlyMap<K, V>
, ReadonlySet<T>
, а также механизм указания модификатора readonly
в аннотации типа.
Массивоподобные readonly типы (модифицировать непосредственно в аннотации типа)¶
TypeScript реализует механизм, позволяющий определять массивы и кортежи как неизменяемые структуры данных. Для этого к типу, указанному в аннотации типа, добавляется модификатор readonly
.
let array: readonly string[] = ['Kent', 'Clark']; // Массив
let tuple: readonly [string, string] = ['Kent', 'Clark']; // Кортеж
Элементы массивоподобных структур, определенных как readonly
, невозможно заменить или удалить. Кроме того, в подобные структуры невозможно добавить новые элементы. Иными словами, у массивоподобных readonly
типов отсутствуют признаки, предназначенные для изменения их содержимого.
В случае объявления readonly
массива становится невозможно изменить его элементы с помощью индексной сигнатуры (array[...]
)
let array: readonly string[] = ['Kent', 'Clark']; // Массив
array[0] = 'Wayne'; // Error, -> Index signature in type 'readonly string[]' only permits reading.
array[array.length] = 'Batman'; // Error -> Index signature in type 'readonly string[]' only permits reading.
Помимо этого, у readonly
массива отсутствуют методы, с помощью которых можно изменить элементы массива.
let array: readonly string[] = ['Kent', 'Clark'];
array.push('Batman'); // Error -> Property 'push' does not exist on type 'readonly string[]'.
array.shift(); // Error -> Property 'shift' does not exist on type 'readonly string[]'.
array.indexOf('Kent'); // Ok
array.map((item) => item); // Ok
С учетом погрешности на известные различия между массивом и кортежем, справедливо утверждать, что правила для readonly
массива справедливы и для readonly
кортежа. Помимо того, что невозможно изменить или удалить слоты кортежа, он также теряет признаки массива, которые способны привести к его изменению.
let tuple: readonly [string, string] = ['Kent', 'Clark'];
tuple[0] = 'Wayne'; // Error -> Cannot assign to '0' because it is a read-only property.
tuple.push('Batman'); // Error -> Property 'push' does not exist on type 'readonly [string, string]'.
tuple.shift(); // Error -> Property 'shift' does not exist on type 'readonly [string, string]'.
tuple.indexOf('Kent'); // Ok
tuple.map((item) => item); // Ok
Также не будет лишним упомянуть, что массив или кортеж, указанный в аннотации с помощью расширенного типа Readonly<T>
, расценивается выводом типов как помеченный модификатором readonly
.
// type A = readonly number[];
type A = Readonly<number[]>;
// type B = readonly [string, boolean];
type B = Readonly<[string, boolean]>;
Благодаря данному механизму в сочетании с механизмом множественного распространения (spread
), становится возможным типизировать сложные сценарии, одним из которых является реализация известной всем функции concat
, способной объединить не только массивы, но и кортежи.
type A = readonly unknown[];
function concat<T extends A, U extends A>(
a: T,
b: U
): [...T, ...U] {
return [...a, ...b];
}
// let v0: number[]
let v0 = concat([0, 1], [2, 3]);
// let v1: [0, 1, 2, 3]
let v1 = concat([0, 1] as const, [2, 3] as const);
// let v2: [0, 1, ...number[]]
let v2 = concat([0, 1] as const, [2, 3]);
// let v3: number[]
let v3 = concat([0, 1], [2, 3] as const);
Напоследок стоит упомянуть, что вывод типов расценивает readonly
массив как принадлежащий к интерфейсу ReadonlyArray<T>
, речь о котором пойдет далее.
ReadonlyArray (неизменяемый массив)¶
Расширенный тип ReadonlyArray<T>
предназначен для создания неизменяемых массивов. ReadonlyArray<T>
запрещает изменять значения массива, используя индексную сигнатуру array[n]
.
let array: ReadonlyArray<number> = [0, 1, 2];
array[0] = 1; // Error -> Index signature in type 'readonly number[]' only permits reading.
array[array.length] = 3; // Error -> Index signature in type 'readonly number[]' only permits reading.
Кроме того, тип ReadonlyArray<T>
не содержит методы, способные изменить, удалить или добавить элементы.
let array: ReadonlyArray<number> = [0, 1, 2];
array.push(3); // Error -> Property 'push' does not exist on type 'readonly number[]'.
array.shift(); // Error -> Property 'shift' does not exist on type 'readonly number[]'.
array.indexOf(0); // Ok
ReadonlyMap (неизменяемая карта)¶
Расширенный тип ReadonlyMap<K, V>
, в отличие от своего полноценного прототипа, не имеет методов, способных его изменить.
let map: ReadonlyMap<string, number> = new Map([
['key', 0],
]);
ReadonlySet (неизменяемое множество)¶
Аналогично другим структурам данных, предназначенных только для чтения, расширенный тип ReadonlySet<T>
не имеет методов, способных его изменить.
let set: ReadonlySet<number> = new Set([0, 1, 2]);