3 способа перебора перечислений в TypeScript

Изучите 3 способа перебора перечислений в TypeScript. Эта статья покажет вам, как использовать встроенные методы объекта, циклы for и библиотеку Lodash для перебора значений перечисления.

Перечисления широко распространены в современном программировании. Обычно они широко используются для моделирования таких категорий вещей, как возможные состояния светофора, дни недели и месяцы года.

Одним из преимуществ перечислений является то, что они позволяют нам отображать короткие списки значений в числа, что упрощает их сравнение и работу с ними в целом. За прошедшие годы перечисления сильно изменились: от простого преобразования «слов» в числа до сложных структур данных, похожих на классы, с методами и параметрами.

В этой статье мы рассмотрим различные подходы к перебору перечислений в TypeScript .

Содержание:

  • Зачем повторять TypeScript ?
  • Встроенные методы объекта
  • Для петель
  • Лодаш

Зачем повторять TypeScript

Перечисления TypeScript — это довольно простые объекты:

enum TrafficLight {
  Green = 1,
  Yellow,
  Red
}

В приведенном выше определении Greenотображается число 1. Последующие члены сопоставляются с целыми числами с автоматическим приращением. Следовательно, Yellowотображается в 2, и Redв 3.

Если бы мы не указали сопоставление Green = 1, TypeScript выбрал бы его 0в качестве начального индекса.

Однако иногда нам нужно перебрать перечисление TypeScript. Это особенно полезно, например, когда мы хотим выполнить какие-то действия для каждого элемента перечисления.

В TypeScript есть несколько способов перебора перечислений. Давайте взглянем.

Встроенные методы объекта

Самый простой способ перебирать перечисление в TypeScript — использовать встроенные методы Object.keys()и Object.values(). Первый возвращает массив, содержащий ключи объектов, а второй — значения.
В следующем фрагменте кода показано, как использовать встроенный метод объектов для получения списка ключей и значений перечисления:

const keys = Object.keys(TrafficLight)

keys.forEach((key, index) => {
        console.log(`${key} has index ${index}`)
})

В приведенном выше примере печатается следующее:

"1 has index 0"
"2 has index 1"
"3 has index 2"
"Green has index 3"
"Yellow has index 4"
"Red has index 5"

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

  • каждому элементу присваивается числовое значение, начинающееся с 0(или с указанного нами числа 1в нашем примере). Это представлено первыми тремя строками в выводе выше.
  • каждому stringзначению присвоен цифровой ключ; это обратное отображение

Если мы хотим перечислить только строковые ключи, нам придется отфильтровать числовые:

const stringKeys = Object
    .keys(TrafficLight)
    .filter((v) => isNaN(Number(v)))

stringKeys.forEach((key, index) => {
    console.log(`${key} has index ${index}`)
})

В этом случае приведенный выше фрагмент печатает следующее:

"Green has index 0"
"Yellow has index 1"
"Red has index 2"

Из вывода выше мы видим, что indexпараметр не имеет ничего общего с фактическим числовым значением в перечислении. Фактически, это просто индекс ключа в массиве, возвращаемый Object.keys().

Аналогичным образом мы можем перебирать значения перечисления:

const values = Object.values(TrafficLight)

values.forEach((value) => {
    console.log(value)
})

Опять же, приведенный выше фрагмент печатает как строковые, так и числовые значения:

"Green"
 "Yellow"
 "Red"
1
2
3

Допустим, нас интересуют числовые значения, а не строковые. Мы можем отфильтровать последние так же, как и раньше, используя .filter((v) => !isNaN(Number(v))).

Стоит отметить, что нам приходится фильтровать значения только потому, что мы имеем дело с числовыми перечислениями. Если бы мы присвоили строковое значение членам нашего перечисления, нам не пришлось бы фильтровать числовые ключи и значения:

enum TrafficLight {
  Green  = "G",
  Yellow = "Y",
  Red        = "R"
}

Object.keys(TrafficLight).forEach((key, index) => {
    console.log(`${key} has index ${index}`)
})

Object.values(TrafficLight).forEach((value) => {
        console.log(value)
})

В приведенном выше фрагменте выводится следующее: первые три строки взяты из первого forEachцикла, а последние три строки — из второго forEachцикла:

"Green has index 0"
"Yellow has index 1"
"Red has index 2"
"G"
"Y"
"R"

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

Использование методов Object.keys()и Object.values()для перебора членов перечислений — довольно простое решение. Тем не менее, он также не очень типобезопасен, поскольку TypeScript возвращает ключи и значения в виде строк или чисел, таким образом не сохраняя типизацию перечисления.

Для петель

Вместо того, чтобы полагаться на Object.keys()и Object.values(), другой подход — использовать forциклы для перебора ключей, а затем использовать обратное сопоставление для получения значений перечисления:

enum TrafficLight {
        Green,
        Yellow,
        Red
}

for (const tl in TrafficLight) {
        const value = TrafficLight[tl]

        if (typeof value === "string") {
                        console.log(`Value: ${TrafficLight[tl]}`)
        }
}

Сценарий выше напечатает следующее:

"Value: Green"
"Value: Yellow"
"Value: Red"

Вы заметите, что в приведенном выше примере мы отфильтровали числовые значения. Таким образом, мы можем извлечь имена членов нашего перечисления. Если бы мы хотели получить их вместо строковых значений, мы могли бы использовать другую защиту в операторе if: typeof value !== "string".

Приведенный выше код не будет работать, если перечисление подкреплено строковыми значениями. В частности, компиляция завершится ошибкой со следующим сообщением об ошибке: Element implicitly has an 'any' type because an expression of type 'string' can't be used to index type 'typeof TrafficLight'. No index signature with a parameter of type 'string' was found on type 'typeof TrafficLight'<./p>

Это связано с тем, что тип valuenow stringи TrafficLight[]принимает только Green, Yellowили Red.

Чтобы это исправить, нам придется явно сообщить TypeScript тип наших ключей:

enum TrafficLight {
        Green  = "G",
        Yellow = "Y",
        Red         = "R"
}

function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] {
        return Object.keys(obj).filter(k => !Number.isNaN(k)) as K[]
}

for (const tl of enumKeys(TrafficLight)) {
        const value = TrafficLight[tl]

        if (typeof value === "string") {
                        console.log(`Value: ${TrafficLight[tl]}`)
        }
}

В приведенном выше примере есть две основные новинки. Во-первых, enumKeysфункция просто извлекает ключи перечисления и возвращает их в виде массива. В приведенном выше примере тип возвращаемого значения enumKeys(TrafficLight)("Green" | "Yellow" | "Red")[]. Во-вторых, в нашем цикле for мы используем for…of, а не . for…inОсновное отличие состоит в том, что последний возвращает значения массива, то есть Green, Yellowи Red. С другой стороны, первый возвращает строковое представление.

Если мы запустим приведенный выше пример, мы, как и ожидалось, получим следующий результат:

"Value: G"
"Value: Y"
"Value: R"

Преимущество этого последнего подхода заключается в том, что тип значения перечисления сохраняется. В частности, в цикле for valueиспользуется тип TrafficLightвместо stringor number.

Лодаш

Lodash — это библиотека JavaScript, предоставляющая множество служебных методов для распространенных задач программирования. Такие методы используют парадигму функционального программирования, позволяющую нам писать более краткий и читабельный код.

Чтобы установить Lodash в наш проект, мы можем запустить следующую команду:

npm install lodash --save

Команда npm install lodashустановит модуль, а saveфлаг обновит содержимое файла package.json.

Оказывается, мы можем использовать этот forInметод для перебора перечисления в TypeScript:

import { forIn } from 'lodash'

enum TrafficLight {
      Green = 1,
      Yellow,
      Red,
}

forIn(TrafficLight, (value, key) => console.log(key, value))

Метод forInперебирает как ключи, так и значения данного объекта, вызывая в простейшей форме заданную функцию для каждой (key, value)пары. По сути, это комбинация методов Object.keys()и Object.values().

Как и следовало ожидать, если мы запустим приведенный выше пример, мы получим как строковые, так и числовые ключи:

1 Green
2 Yellow
3 Red
Green 1
Yellow 2
Red 3

Как и раньше, мы можем легко отфильтровать строковые или числовые ключи, в зависимости от наших потребностей:

import { forIn } from 'lodash'

enum TrafficLight {
        Green = 1,
        Yellow,
        Red,
}

forIn(TrafficLight, (value, key) => {
        if (isNaN(Number(key))) {
                  console.log(key, value)
        }
})

В приведенном выше примере печатается следующее:

Green 1
Yellow 2
Red 3

В этом случае тип keyis string, тогда как тип valueis TrafficLight. Следовательно, это решение сохраняет типизацию value:

import { forIn } from 'lodash'

enum TrafficLight {
    Green  = "G",
    Yellow = "Y",
    Red     = "R"
}

forIn(TrafficLight, (value, key) => {
    if (isNaN(Number(key))) {
              console.log(key, value)
    }
})

Как и следовало ожидать, приведенный выше пример печатает следующее:

Green G
Yellow Y
Red R

Заключение

В этой статье мы рассмотрели несколько способов перебора ключей и значений типов перечислений в TypeScript. Во-первых, мы использовали встроенные методы любого объекта TypeScript, отметив, что они достаточно «низкоуровневые».

Во-вторых, мы перешли к подходу более высокого уровня с forциклами. Мы убедились, что можем научить TypeScript сохранять типизацию, заданную перечислениями, не полагаясь на строковое или числовое представление. В-третьих, мы рассмотрели удобный метод библиотеки Lodash — forIn.

Для каждого из рассмотренных нами подходов мы проанализировали различия между числовыми и строковыми перечислениями. Как обычно, не существует единственного «правильного» решения. Способ перебора ключей/значений ваших перечислений сильно зависит от того, что вам нужно делать и хотите ли вы сохранить типизацию перечисления.

Источник: https://blog.logrocket.com

#typescript

1.05 GEEK