Перейти к содержанию

Массивоподобные readonly типы, ReadonlyArray, ReadonlyMap, ReadonlySet

Чем меньше шансов случайного изменения значений, определенных в объектных типах, тем больше программа защищена от ошибок во время выполнения. Очередным шагом в этом направлении стали неизменяемые массивоподобные типы ReadonlyArray<T>, ReadonlyMap<K, V>, ReadonlySet<T>, а также механизм указания модификатора readonly в аннотации типа.

Массивоподобные readonly типы (модифицировать непосредственно в аннотации типа)

TypeScript реализует механизм, позволяющий определять массивы и кортежи как неизменяемые структуры данных. Для этого к типу, указанному в аннотации типа, добавляется модификатор readonly.

1
2
let array: readonly string[] = ['Kent', 'Clark']; // Массив
let tuple: readonly [string, string] = ['Kent', 'Clark']; // Кортеж

Элементы массивоподобных структур, определенных как readonly, невозможно заменить или удалить. Кроме того, в подобные структуры невозможно добавить новые элементы. Иными словами, у массивоподобных readonly типов отсутствуют признаки, предназначенные для изменения их содержимого.

В случае объявления readonly массива становится невозможно изменить его элементы с помощью индексной сигнатуры (array[...])

1
2
3
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 массива отсутствуют методы, с помощью которых можно изменить элементы массива.

1
2
3
4
5
6
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 кортежа. Помимо того, что невозможно изменить или удалить слоты кортежа, он также теряет признаки массива, которые способны привести к его изменению.

1
2
3
4
5
6
7
8
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.

1
2
3
4
5
// type A = readonly number[];
type A = Readonly<number[]>;

// type B = readonly [string, boolean];
type B = Readonly<[string, boolean]>;

Благодаря данному механизму в сочетании с механизмом множественного распространения (spread), становится возможным типизировать сложные сценарии, одним из которых является реализация известной всем функции concat, способной объединить не только массивы, но и кортежи.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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].

1
2
3
4
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> не содержит методы, способные изменить, удалить или добавить элементы.

1
2
3
4
5
6
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>, в отличие от своего полноценного прототипа, не имеет методов, способных его изменить.

1
2
3
let map: ReadonlyMap<string, number> = new Map([
  ['key', 0],
]);

ReadonlySet (неизменяемое множество)

Аналогично другим структурам данных, предназначенных только для чтения, расширенный тип ReadonlySet<T> не имеет методов, способных его изменить.

1
let set: ReadonlySet<number> = new Set([0, 1, 2]);

Комментарии