Перейти к содержимому
Теория · Массивы и кортежи · урок 1 из 7

Типы массивов

Чему вы научитесь

Чему вы научитесь:

  • записывать тип массива как number[] и читать тип, который TypeScript вывел сам;
  • запрещать изменения массива типом readonly number[];
  • описывать кортеж — массив, где у каждого места свой тип, например [string, number];
  • сохранять в типе точные значения массива и объекта через as const.
Ряд одинаковых шкафчиков number[] и карточка с двумя ячейками [0] и [1]

Представьте ряд шкафчиков в камере хранения. Над рядом висит табличка «Только для чемоданов». Шкафчики пронумерованы с нуля, как элементы массива, и в каждом лежит одно и то же — чемодан. Служащий, который открывает любой из них, заранее знает, что достанет.

В JavaScript у массива такой таблички нет. В него можно положить что угодно, и ошибка всплывёт позже, например при подсчёте. Вот программа, которая складывает баллы:

TypeScript
const scores = [90, 75];
scores.push("100"); // ✗
let total = 0;
for (const s of scores) {
    total += s;
}
console.log(total);
TEXT
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

tsc сообщает: аргумент типа string нельзя передать в параметр типа number. TypeScript посмотрел на литерал [90, 75] и повесил над массивом табличку «только числа», поэтому строка "100" в push не прошла.

Если бы этот JavaScript запустили, программа вывела бы 165100. К числу 165 прибавилась строка "100", и оператор + склеил их в одну строку. TypeScript находит такую ошибку на строке с push, а не там, где всплыл странный результат.

Тип «массив чисел»

Тип массива записывают как тип элемента и пару квадратных скобок: number[] читается «массив чисел», string[] — «массив строк». Это и есть табличка над рядом шкафчиков: она говорит, что лежит в каждом элементе.

Для const scores = [90, 75] TypeScript вывел тип number[] сам, и подсказка редактора покажет const scores: number[]. Писать тип вручную нужно там, где выводить его не из чего, например у параметра функции:

TypeScript
function average(nums: number[]): number {
    let sum = 0;
    for (const n of nums) {
        sum += n;
    }
    return sum / nums.length;
}

console.log(average([90, 75, 60])); // 75

Давайте разберём пример по строкам:

  1. nums: number[] — функция принимает массив чисел, и вызов с массивом строк не скомпилируется.
  2. В цикле for…of у n тип number: TypeScript берёт его с таблички массива.
  3. sum / nums.length — число, поэтому результат совпадает с аннотацией number.
  4. Программа печатает 75.

Тип массива проверяет каждое место, где массив получает элемент или отдаёт его:

  • scores.push(x) принимает только x типа number;
  • запись по индексу scores[0] = "100" тоже не пройдёт: ошибка TS2322, строку нельзя присвоить типу number;
  • чтение scores[0] даёт number, поэтому у элемента можно вызвать toFixed и другие методы числа.

Две записи одного типа

Тип массива чисел можно записать двумя способами.

Запись number[]

Короткая запись: сначала тип элемента, потом []. Её чаще встречают в коде, и в курсе используется она.

Запись Array<number>

Длинная запись: слово Array и тип элемента в угловых скобках. Это тот же самый тип. Если написать const nums: Array<number> = [1, 2];, подсказка редактора всё равно покажет number[].

На собеседованиях иногда спрашивают, чем number[] отличается от Array<number>. Ничем: это две записи одного типа, и выбор между ними — вопрос стиля команды.

Массив из разных значений

Если в литерале лежат значения разных типов, TypeScript выводит массив объединения. Для const mixed = ["Аня", 30]; подсказка покажет (string | number)[]: каждый элемент — строка или число.

Скобки здесь меняют смысл:

  • (string | number)[] — массив, в котором любой элемент — строка или число;
  • string | number[] — одна строка или массив чисел.

Табличка над таким рядом гласит «чемоданы или сумки». Достав вещь из шкафчика, служащий не знает, что у него в руках, и может делать с ней только то, что годится и для чемодана, и для сумки. С элементом mixed[0] так же: TypeScript разрешает только общее для string и number.

Здесь аналогия с камерой хранения заканчивается. Число шкафчиков в ряду задано заранее, а массив растёт: каждый push пристраивает в конец ряда новый шкафчик с той же табличкой.

Пустой массив

Из самого литерала [] вывести тип элементов не из чего: в нём нет ни одного элемента. Поэтому у пустого массива тип пишут сами: const names: string[] = [];.

Без аннотации TypeScript ведёт себя по-разному, смотря где лежит пустой массив:

  • в переменной он следит за следующими строками: после const names = []; и names.push("Аня"); у names тип string[];
  • но пока в массив ничего не положили, его нельзя ни передать в функцию, ни взять из него элемент: tsc сообщит о неявном типе any[];
  • в свойстве объекта TypeScript за следующими строками не следит и сразу выводит never[].

Частая ошибка

Пустой массив положили в свойство объекта и не написали тип:

TypeScript
const cart = { items: [] };
cart.items.push("apple"); // ✗
TEXT
error TS2345: Argument of type '"apple"' is not assignable to parameter of type 'never'.

У свойства items TypeScript вывел тип never[] — массив, в который нельзя положить ничего: тип never не принимает ни одного значения. Поэтому сообщение и говорит, что аргумент типа "apple" нельзя передать в параметр типа never.

Исправление — заранее назвать тип элементов:

TypeScript
type Cart = { items: string[] };

const cart: Cart = { items: [] };
cart.items.push("apple");
console.log(cart.items.length); // 1

Главное

  • Тип массива — тип элемента и скобки: number[]; Array<number> — другая запись того же типа.
  • TypeScript выводит тип из литерала: [90, 75] — это number[], и push("100") в него — ошибка TS2345.
  • Литерал из значений разных типов даёт массив объединения: (string | number)[].
  • Пустому массиву тип пишут сами, иначе в свойстве объекта он станет never[].