В TypeScript те же циклы, что и в JavaScript: for, for...of, for...in, while, do...while и метод массива forEach. TypeScript добавляет к ним типы: переменная цикла for...of получает тип элемента, а ключ в for...in всегда имеет тип string.
Для большинства циклов по массиву стоит брать for...of: он хорошо читается, поддерживает break, continue и await и не требует аннотаций.
Классический цикл for
for (init; condition; update) подходит для счёта, шага больше единицы или прохода в обратном направлении. TypeScript выводит тип счётчика из начального значения.
Объявляйте счётчик через let, а не var. С let каждая итерация получает свою привязку, и колбэк, созданный в цикле, видит значение своей итерации.
for...of: значения массивов, строк и Map
for...of работает с любым итерируемым объектом, и переменная цикла получает соответствующий тип. Чтобы получить ещё и индекс, перебирайте array.entries(), который выдаёт кортежи [index, value].
break выходит из цикла, а continue переходит к следующему элементу, точно как в цикле for. Подробности времени выполнения (итерируемые объекты, генераторы) на странице JavaScript for...of и for...in.
forEach: без break и без await
forEach вызывает функцию для каждого элемента и возвращает undefined. Параметры колбэка получают типы из массива, так что аннотации не нужны. Двух вещей он не умеет:
- Остановиться раньше.
breakвнутри колбэка даёт ошибку компиляции TS1107,Jump target cannot cross function boundary.returnзавершает только текущий вызов, то есть работает какcontinue. - Дождаться асинхронной работы.
forEachигнорирует промис, который возвращаетasync-колбэк.
Асинхронную ловушку легко пропустить, потому что код компилируется без предупреждений:
Колбэки forEach завершаются в порядке таймеров (10, 20, 30), когда цикл уже пошёл дальше. Версия с for...of выполняет их по очереди. Для параллельной работы, которую всё равно нужно дождаться, используйте await Promise.all(items.map(async (item) => { ... })).
for...in: ключи это строки
for...in перебирает имена перечисляемых свойств объекта. Тип ключа всегда string, даже для массивов, где ключами служат индексы в виде строк. Из-за этого индексировать им типизированный объект неудобно:
index.ts(5,22): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ name: string; age: number; }'.
No index signature with a parameter of type 'string' was found on type '{ name: string; age: number; }'.
TypeScript намеренно типизирует ключ как string: во время выполнения у объекта может быть больше свойств, чем перечислено в его типе (структурная типизация допускает лишние), а for...in проходит и по унаследованным перечисляемым свойствам. Поэтому он не может обещать, что ключ это "name" | "age". Способы исправить описаны в следующем разделе. Для массивов for...in не используйте вообще.
Перебор ключей и значений объекта
Object.entries даёт пары [key, value] и считается обычным способом перебрать объект. Object.keys возвращает string[] по той же причине, что и for...in, поэтому, если вы знаете, что лишних ключей у объекта нет, укажите тип ключа утверждением.
Для данных, ключи которых заранее неизвестны, типизируйте объект как Record<string, number> или используйте Map; оба варианта описаны на странице словарь.
while и do...while
while проверяет условие перед каждым проходом; do...while сначала один раз выполняет тело, а потом проверяет. Они подходят для циклов, которые не обходят коллекцию, например для чтения до выполнения условия. Сужение типов внутри них работает: после проверки !== undefined в условии переменная в теле сужена.
Метки, break и continue во вложенных циклах
Обычный break выходит только из самого внутреннего цикла. Чтобы выйти из внешнего, пометьте его меткой и укажите её:
Какой цикл выбрать
| Задача | Цикл |
|---|---|
| Каждый элемент массива | for (const x of arr) |
| Элемент и индекс | for (const [i, x] of arr.entries()) или arr.forEach((x, i) => ...) |
| Остановиться раньше | for...of с break или find, some, every |
| Ждать каждый шаг | for...of с await внутри |
| Построить новый массив | map, filter (не цикл) |
| Считать или шагать по n | for (let i = 0; ...; i += n) |
| Ключи и значения объекта | for (const [k, v] of Object.entries(obj)) |
| Записи Map | for (const [k, v] of map) |
| До выполнения условия | while |
Часто задаваемые вопросы
Как написать цикл for в TypeScript?
Так же, как в JavaScript: for (let i = 0; i < 5; i++) { ... }; TypeScript выводит для i тип number. Чтобы пройти по элементам массива, короче написать for (const item of items), и item получит тип элемента массива.
Как выйти из forEach в TypeScript?
Никак: break внутри колбэка даёт ошибку компиляции TS1107 (Jump target cannot cross function boundary), а return завершает только текущий вызов колбэка, как continue. Используйте for...of с break или some/find/every, которые по замыслу останавливаются раньше.
Чем for...of отличается от for...in в TypeScript?
for...of проходит по значениям итерируемого объекта (элементам массива, символам строки, записям Map) с их настоящими типами. for...in проходит по перечисляемым ключам свойств объекта, всегда с типом string, в том числе по индексам массива в виде строк. Для массивов используйте for...of, для объектов Object.entries.
Как перебрать ключи и значения объекта в TypeScript?
Используйте for (const [key, value] of Object.entries(obj)). key имеет тип string, а value это объединение типов свойств. Если key нужен с типом keyof typeof obj, используйте утверждение: (Object.keys(obj) as (keyof typeof obj)[]), потому что TypeScript намеренно типизирует Object.keys как string[].
Можно ли использовать await внутри forEach?
Это скомпилируется, но forEach не ждёт промисы, которые возвращает колбэк, поэтому цикл завершается раньше, чем работа. Для последовательной работы используйте for...of с await внутри, а для параллельной await Promise.all(items.map(async (x) => ...)).