Уменьшение, сопоставление и фильтр — единственные функции, которые я обычно использую для работы с массивами, иногда с использованием for..of, когда есть определенный асинхронный вариант использования. Я часто советую людям полностью забыть о существовании forEach. Это замаскированный цикл for, который почти всегда приводит к изменению переменных, что затрудняет отладку кода.

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

  1. Чтобы добавить в схему данных, мы можем использовать map. При работе с [Object] вы можете обновлять свойства в каждом индексе. При работе с [string|number] вы можете манипулировать каждым индексом
  2. Чтобы отфильтровать значения из массива, мы можем использовать filter. Когда вам нужно подмножество данных из одного массива в другой на основе некоторых критериев.
  3. Чтобы создать/изменить схему данных, мы можем использовать reduce. Подробнее об этом в этой статье.

Чаще всего я вижу, как разработчики правильно используют фильтр и карту (за исключением того, что карта нечетного времени используется в качестве замены для forEach), в то время как reduce практически не используется. Я надеюсь объяснить, где и когда использовать reduceи почему в большинстве случаев это лучший вариант.

Уменьшить › forEach

Во-первых, давайте посмотрим, как я обычно вижу, как люди манипулируют массивом без использования reduce.

type User = {
 name: string;
 email: string;
 role: string;
}
type Option = {
 label: string;
 value: User
}
const getUserDropdownOptions = (users: User) => {
  const userOptions: Option[] = [];
  users.forEach((user) => {
    userOptions.push({
     label: `${user.name} <${user.email}>`
     value: user,
    })
   });
  return userOptions;
}

В этом примере мы берем массив пользователей и создаем массив параметров для использования в раскрывающемся списке. Разбираем происходящее:

  1. Создайте возвращаемый массив с именем userOptions, чтобы поместить в него новую схему данных Option.
  2. Прокрутите users и используйте данные для создания типа данных Option.
  3. Возврат userOptions

На этом примере вы можете посмотреть, как работает reduce. Если мы переименуем userOptions в acc (сокращение от аккумулятора), этот код будет почти идентичен.

const getUserDropdownOptions = (users: User) => 
 users.reduce<Option[]>((acc, user) => {
  acc.push({
   label: `${user.name} <${user.email}>`
   value: user,
  });
  return acc;
 }, [])

Этот код дает тот же результат, что и пример forEach выше, за исключением использования сокращения. Давайте посмотрим на некоторые из ключевых отличий

  1. Поскольку reduce возвращает Option[], нет необходимости в {} обертке функции. Это кажется не очень важным в таком маленьком примере, как этот, но когда у вас есть несколько таких функций в файле, это помогает навести порядок.
  2. Опять же, поскольку reduce уже возвращает Option[], дополнительная переменная userOptions не нужна. Вместо этого вы можете использовать переменную-аккумулятор (acc) в функции обратного вызова сокращения.

Некоторые из вас, знакомые с сокращением, могли заметить более компактный способ записи возврата из сокращения

users.reduce<Option[]>((acc, user) => [
  ...acc,
  {
   label: `${user.name} <${user.email}>`
   value: user,
  }
], [])

Я перестал записывать свои отчеты таким образом, потому что при работе с большими структурами данных это оказывает более существенное влияние на производительность, чем вы думаете. Выполнение этого метода деконструкции в редукционном редукторе замедлило приложение, над которым я работал, на 500%, 50 мс -> 10 мс. Эта оптимизация кажется не такой уж большой, но когда вся функция сокращения выполняется за 500 мс, а оптимизаций такого размера несколько, вы переходите от медлительности к быстроте всего за несколько строк кода.

«Уменьшить» потребность в let

Если мне нужно написать let в своем коде, это я меняю const на let переменной по определенной причине. Есть несколько причин использовать let, а с reduce в вашем наборе инструментов вы найдете еще меньше вариантов использования.

В тех случаях, когда у вас есть массив примитивов и вы хотите манипулировать ими, forEach вам нужно будет использовать переменную let для отслеживания вещей. Давайте посмотрим на добавление значений из массива

const arr = [1, 2, 3, 4];
const addArrayValues = (arr: number[]) => {
 let sum = 0;
 arr.forEach((num) => {
  sum = sum + num
 })
 
 return sum
}

У нас есть функция, которая принимает number[] и возвращает сумму всех чисел в массиве. Поток очень похож на первый пример.

  1. Создайте возвращаемую переменную. На каждой итерации sum необходимо переназначать, поэтому мы должны использовать let.
  2. Перебрать массив и изменить это возвращаемое значение
  3. Вернуть эту переменную

Теперь устранить необходимость в let здесь так же просто, как использовать reduce

const addArrayValues = (arr: number[]) =>
 arr.reduce((sum, num) => sum + num, 0)

Написание такого кода не связано с игрой в гольф, иначе я бы назвал переменные a s и n. Я говорю о написании чистого, неизменного кода, который легко понять, когда вы чувствуете себя комфортно с reduce.

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

type User = {
 name: string;
 email: string;
 postCounts: {
  tiktok: number;
  twitter: number;
 }
}
type PostCount = {
 tiktok: number;
 total: number;
 twitter: number;
}
const defaultCount: PostCount = {
 tiktok: 0
 total: 0,
 twitter: 0,
}
const getTotalPostCount = (users: User[]) => 
 users.reduce<PostCount>((acc, user) => {
  const { postCounts: { tiktok, twitter } } = user
  const userTotal = tiktok + twitter;
  
  return {
   tiktok: acc.tiktok + tiktok,
   total: acc.total + userTotal,
   twitter: acc.twitter + twitter
  }
 }, defaultCount)

Мы отслеживаем итоги для каждой социальной платформы и общий итог. Использование сокращения здесь не позволяет нам иметь 3 переменные let, которые в противном случае нам пришлось бы использовать, если бы они были записаны с forEach.

Надеюсь, это придаст вам больше уверенности при попытке заменить ваши старые функции forEach на reduce. Начните с малого, окуните пальцы ног, прежде чем пытаться стать умнее, и напишите основные логические части с помощью reduce