Преобразование коллекции
Преобразование берёт коллекцию, например массив или Map, и строит из неё новую коллекцию. Исходная коллекция не изменяется.
Массивы в TypeScript уже содержат большинство таких операций в виде методов: map, filter, flat, flatMap и join. Вы вызываете их на массиве и получаете новое значение, поэтому цикл не нужен. У некоторых операций, например у zip, встроенного метода нет — для них один раз пишут маленькую вспомогательную функцию и переиспользуют её.
Вспомогательная функция может быть обобщённой. Обобщённая функция работает с любым типом элементов: вы один раз пишете function zip<A, B>(first: A[], second: B[]): [A, B][] и используете её и для чисел, и для строк, и для объектов. Параметры типа A и B подставляет компилятор в месте вызова.
Map
Операция map применяет одну и ту же функцию к каждому элементу и собирает ответы в новый массив той же длины. Например, она может превратить [ 1, 2, 3 ] в [ 3, 6, 9 ].
У массивов есть встроенный метод map. Вы передаёте ему функцию, и он возвращает новый массив. Тип результата следует за функцией: если функция возвращает string, вы получаете string[].
Не путайте это с классом Map, который хранит пары ключ — значение. Здесь «map» означает «применить функцию к каждому элементу».
Отображение каждого элемента
numbers.map(x => x * 3) вызывает функцию для каждого элемента и возвращает новый массив. Исходный массив numbers тоже выводится, чтобы показать, что он по-прежнему содержит [ 1, 2, 3 ].
Вывод: программа на TypeScript и захваченный вывод
const numbers = [1, 2, 3]
// map вызывает функцию по одному разу для каждого элемента
const tripled = numbers.map(x => x * 3)
console.log(numbers)
console.log(tripled)
Отображение с индексом
Иногда важна и позиция элемента. Функция обратного вызова map получает индекс вторым аргументом, поэтому можно использовать оба значения. Здесь каждое значение умножается на собственный индекс, и первый элемент становится равен 0.
Вывод: программа на TypeScript и захваченный вывод
const numbers = [1, 2, 3]
// второй аргумент функции обратного вызова — позиция элемента
const result = numbers.map((value, idx) => value * idx)
console.log(result)
Пропуск части результатов
Иногда часть результатов нужно отбросить. Пусть функция обратного вызова возвращает undefined для таких элементов, а затем уберите их с помощью filter. Вспомогательная функция isDefined — это защитник типа: её тип возврата value is T сообщает компилятору, что каждое значение, прошедшее фильтр, больше не является undefined. Это сужает тип результата с (number | undefined)[] до number[].
Вывод: программа на TypeScript и захваченный вывод
const numbers = [1, 2, 3]
// функция обратного вызова возвращает undefined для элементов, которые надо убрать
const maybe = numbers.map(x => (x === 2 ? undefined : x * 3))
// защитник типа сообщает компилятору, что оставленные значения никогда не undefined
function isDefined<T>(value: T | undefined): value is T {
return value !== undefined
}
const result: number[] = maybe.filter(isDefined)
console.log(maybe)
console.log(result)
Преобразование ключей
Чтобы изменить ключи Map, разверните его в массив пар [key, value] с помощью [...numbersMap], отобразите каждую пару в новую пару и передайте результат в new Map(...). Здесь toUpperCase() изменяет каждый ключ. Map хранит записи в порядке вставки, поэтому вывод всегда идёт в одном и том же порядке.
Вывод: программа на TypeScript и захваченный вывод
const numbersMap = new Map([["key1", 1], ["key2", 2]])
// разворачиваем Map в пары, меняем каждый ключ, строим новый Map
const upperKeys = new Map(
[...numbersMap].map(([key, value]): [string, number] => [key.toUpperCase(), value])
)
console.log(numbersMap)
console.log(upperKeys)
Преобразование значений
Чтобы изменить значения, сделайте то же самое, но сохраните ключ и вычислите новое значение. Здесь каждое значение увеличивается на длину своего ключа: 1 + "key1".length равно 5.
Вывод: программа на TypeScript и захваченный вывод
const numbersMap = new Map([["key1", 1], ["key2", 2]])
// сохраняем ключ, вычисляем новое значение
const newValues = new Map(
[...numbersMap].map(([key, value]): [string, number] => [key, value + key.length])
)
console.log(newValues)
Zip
Zip берёт два массива и соединяет их элементы по позициям: первый с первым, второй со вторым и так далее. В результате получается столько пар, сколько элементов в более коротком массиве.
Встроенного метода zip нет, поэтому обычное решение — маленькая вспомогательная функция. Пара записывается как кортежный тип [A, B]: массив фиксированной длины, где у каждой позиции свой тип. Найдите меньшую длину с помощью Math.min и пройдите циклом по индексу, строя пары.
Соединение двух массивов
Обобщённая вспомогательная функция zip возвращает здесь [string, string][]: массив кортежей из двух элементов. Цикл идёт до меньшей длины и строит по одному кортежу на индекс. При выводе каждая пара показана как отдельный маленький массив.
Вывод: программа на TypeScript и захваченный вывод
// zip соединяет элементы по позициям и останавливается на более коротком массиве
function zip<A, B>(first: A[], second: B[]): [A, B][] {
const n = Math.min(first.length, second.length)
const pairs: [A, B][] = []
for (let i = 0; i < n; i++) {
pairs.push([first[i], second[i]])
}
return pairs
}
const colors = ["рыжий", "коричневый", "серый"]
const animals = ["лис", "медведь", "волк"]
const pairs = zip(colors, animals)
console.log(pairs)
Соединение с преобразованием
Сами пары нужны не всегда. Здесь цикл сразу использует оба элемента, чтобы построить предложение. animals[i][0] — это первая буква, переведённая в верхний регистр с помощью toUpperCase(), а animals[i].slice(1) — остаток слова. Предложение собирается с помощью шаблонной строки.
Вывод: программа на TypeScript и захваченный вывод
const colors = ["рыжий", "коричневый", "серый"]
const animals = ["лис", "медведь", "волк"]
const n = Math.min(colors.length, animals.length)
for (let i = 0; i < n; i++) {
// делаем заглавной только первую букву названия животного
const name = animals[i][0].toUpperCase() + animals[i].slice(1)
console.log(`${name} — ${colors[i]}`)
}
Разделение массива пар
Unzip — обратная операция: один массив пар превращается в два массива. Цикл for...of деструктурирует каждый кортеж в word и value и добавляет части в два отдельных массива.
Вывод: программа на TypeScript и захваченный вывод
const numberPairs: [string, number][] = [["one", 1], ["two", 2], ["three", 3]]
const words: string[] = []
const values: number[] = []
// деструктуризация даёт имя обеим частям пары
for (const [word, value] of numberPairs) {
words.push(word)
values.push(value)
}
console.log(words)
console.log(values)
Associate
Ассоциирование строит Map из массива. Каждый элемент даёт один ключ и одно значение. Map не может хранить один и тот же ключ дважды, поэтому если два элемента дают одинаковый ключ, побеждает более поздний.
Короткий способ — применить map к каждому элементу, превратив его в кортеж [key, value] и передать полученный массив в new Map(...). Укажите тип возврата функции обратного вызова как [string, number], чтобы компилятор увидел кортеж, а не обычный массив.
Элементы как ключи
Сам элемент становится ключом, а значение вычисляется из него. Здесь слова — это ключи, а их длины — значения, поэтому lengths.get("three") равно 5.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three"]
// каждое слово становится ключом, его длина — значением
const lengths = new Map(words.map((word): [string, number] => [word, word.length]))
console.log(lengths)
console.log(lengths.get("three"))
Элементы как значения
Элемент становится значением, а ключ вычисляется из него. Здесь ключ — это первая буква в верхнем регистре. И two, и three начинаются с T, поэтому three заменяет two, и размер Map равен 2.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three"]
// ключ — первая буква; более позднее слово заменяет более раннее
const byFirstLetter = new Map(
words.map((word): [string, string] => [word[0].toUpperCase(), word])
)
console.log(byFirstLetter)
console.log(byFirstLetter.size)
Вычисление и ключа, и значения
Из элемента можно вычислить и ключ, и значение. Здесь ключ — это первая буква, а значение — длина слова. Снова побеждает более поздняя запись с ключом T, поэтому T хранит 5 — длину слова three.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three"]
// ключ — первая буква, значение — длина слова
const lengthByLetter = new Map(
words.map((word): [string, number] => [word[0].toUpperCase(), word.length])
)
console.log(lengthByLetter)
Ключ и значение из одного элемента
Иногда один элемент содержит обе части. split(" ") разрезает полное имя по пробелу на массив слов. Фамилия становится ключом, а имя — значением.
Вывод: программа на TypeScript и захваченный вывод
const names = ["Alice Adams", "Brian Brown"]
// один элемент содержит обе части: фамилия -> имя
const byLastName = new Map(
names.map((fullName): [string, string] => {
const parts = fullName.split(" ")
return [parts[1], parts[0]]
})
)
console.log(byLastName)
Разглаживание
Разглаживание превращает вложенную коллекцию, например массив массивов, в один обычный массив со всеми элементами по порядку.
У массивов есть для этого два метода. flat() разглаживает один уровень существующего массива массивов. flatMap(f) делает оба шага сразу: отображает каждый элемент в массив, а затем разглаживает результаты, что удобно, когда внутренние массивы получаются из самих элементов.
Разглаживание массива массивов
numberSets.flat() копирует элементы каждого вложенного массива в один новый массив. Два вложенных массива по три числа превращаются в один массив из шести элементов.
Вывод: программа на TypeScript и захваченный вывод
const numberSets = [[1, 2, 3], [4, 5, 6]]
// flat() копирует элементы каждого вложенного массива в один новый массив
const result = numberSets.flat()
console.log(result)
console.log(result.length)
Разглаживание значений из объектов
Каждый объект Container хранит массив в поле values. flatMap(c => c.values) читает это поле у каждого контейнера и объединяет все массивы в один. Восемь значений из трёх контейнеров оказываются вместе, и они выводятся объединёнными через запятые, чтобы поместиться в одну строку.
Вывод: программа на TypeScript и захваченный вывод
interface Container {
values: string[]
}
const containers: Container[] = [
{ values: ["one", "two", "three"] },
{ values: ["four", "five"] },
{ values: ["six", "seven", "eight"] }
]
// flatMap превращает каждый элемент в массив и разглаживает результаты
const allValues = containers.flatMap(c => c.values)
console.log(allValues.length)
console.log(allValues.join(", "))
Строковое представление
Часто коллекцию нужно показать как читаемый текст, например с запятыми между элементами.
Метод join принимает разделитель и возвращает одну строку. Он сам превращает каждый элемент в текст, поэтому для чисел дополнительный шаг не нужен. У join нет других настроек, кроме разделителя, поэтому дополнения — префикс, постфикс, ограничение или преобразование — делаются вокруг него: с помощью +, slice или map перед объединением.
Объединение с разделителем
words.join(", ") помещает ", " между словами и возвращает одну строку.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three"]
// join вставляет разделитель между элементами
console.log(words.join(", "))
Добавление префикса и постфикса
Префикс и постфикс — это просто текст вокруг объединённого результата. Вы добавляете их с помощью +.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three"]
// префикс и постфикс — это просто текст вокруг объединённого результата
const text = "начало: " + words.join(" | ") + ": конец"
console.log(text)
Ограничение количества элементов
Для длинного массива вы можете захотеть показать только первые несколько элементов. Возьмите первые 10 с помощью numbers.slice(0, 10) и объедините их — join сам превращает числа в текст. Затем добавьте в конце ", <...>", чтобы показать, что остальные элементы отброшены.
Вывод: программа на TypeScript и захваченный вывод
const numbers: number[] = []
for (let i = 1; i <= 100; i++) {
numbers.push(i)
}
// slice берёт первые 10 элементов; join превращает числа в текст
const limit = 10
const shown = numbers.slice(0, limit).join(", ")
const text = numbers.length > limit ? shown + ", <...>" : shown
console.log(text)
Преобразование каждого элемента перед объединением
Чтобы изменить каждый элемент перед объединением, сначала примените к массиву map, а затем вызовите join на результате. Здесь каждое слово становится "Элемент: " плюс слово в верхнем регистре.
Вывод: программа на TypeScript и захваченный вывод
const words = ["one", "two", "three"]
// сначала меняем каждый элемент, затем объединяем результаты
const parts = words.map(word => "Элемент: " + word.toUpperCase())
console.log(parts.join(", "))