С дополнительным исследованием альтернативы

В TypeScipt перечисления или перечисляемые типы — это специальные типы данных, которые могут иметь набор констант. Хотя концепция перечислений не является уникальной для Typescript, тот факт, что она в конечном итоге компилируется в JavaScript, делает перечисления интересными для Typescript.

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

Итак, приступим:

Перечисления TypeScript

Самый простой способ определить перечисление в TypeScript выглядит следующим образом:

enum Protocol {
  HTTP,
  HTTPS,
  WS
}

Когда мы делаем это, TypeScript автоматически присваивает числовые значения этим перечисляемым константам, начиная с 0.

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

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

function printProtocol (protocol:Protocol):void {
  switch(protocol){
    case Protocol.HTTP:
      console.log("web insecure");
      break;
    case Protocol.HTTPS:
      console.log("web secure");
      break;
    case Protocol.WS:
      console.log("socket protocol");
      break;
    default:
      console.log("Unknown protocol");
  }
}

printProtocol(Protocol.HTTP);

Проблема

JavaScript сам по себе еще не имеет концепции перечислений. Для него есть предложение TC39, но на момент написания этого поста он еще не был частью основного языка.

Итак, когда код Typescript компилируется в JavaScript, перечисления превращаются в двунаправленно доступные объекты следующим образом:

{
  0: "HTTP",
  1: "HTTPS",
  2: "WS",
  HTTP: 0,
  HTTPS: 1,
  WS: 2
}

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

Перечисления со строковыми значениями

Давайте назначим строковые значения перечислениям, которые мы создали в предыдущем разделе:

enum Protocol {
  HTTP = "http",
  HTTPS = "https",
  WS = "websocket"
}

Теперь это будет скомпилировано в следующий объект JavaScript:

{
  HTTP: "http",
  HTTPS: "https",
  WS: "websocket",
}

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

В зависимости от нашего варианта использования это непоследовательное поведение может стать проблемой, а может и не стать.

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

Константное утверждение

Ключевое слово as const в Typescript, известное как const assertion, можно использовать в качестве альтернативы перечислениям для создания более согласованного скомпилированного JavaScript, независимо от типа значений, присвоенных константам, назначенным для сборник констант.

Это можно сделать следующим образом:

const Protocol  = {
  HTTP: 0,
  HTTPS: 1,
  WS: 2
} as const;

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

Скомпилированный JavaScript приведет к следующему объекту:

{
  HTTP: 0,
  HTTPS: 1,
  WS: 2
}

Получение типов из утверждений Const

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

'Protocol' refers to a value, but is being used as a type here. 
Did you mean 'typeof Protocol'?

В TypeScript есть способ обойти эту проблему.

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

const Protocol  = {
  HTTP: 0,
  HTTPS: 1,
  WS: 2
} as const;

type Protocol = keyof typeof Protocol;
type ProtocolValues = typeof Protocol[keyof typeof Protocol]

function printProtocol (protocol:ProtocolValues):void {
  switch(protocol){
    case Protocol.HTTP:
      console.log("web insecure");
      break;
    case Protocol.HTTPS:
      console.log("web secure");
      break;
    case Protocol.WS:
      console.log("socket protocol");
      break;
    default:
      console.log("Unknown protocol");
  }
}

printProtocol(Protocol.HTTP);

тип Protocol может иметь возможные значения "HTTP"|"HTTPS"|"WS" , а тип ProtocolValues может быть 0|1|2

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

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

const Protocol  = {
  HTTP: "http",
  HTTPS: "https",
  WS: "websocket"
} as const;

И наш производный тип ProtocolValues будет обновлен до "http"|"https"|"websocket".

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

const Protocol  = {
  HTTP: "http",
  HTTPS: 2,
  WS: "websocket"
} as const;

Это обновит наш тип ProtocolValues до суженного типа "http"|2|"websocket".

Демонстрационное видео

Заключение

Мы увидели, что такое перечисления в Typescript и как они компилируются в код JavaScript, а также изучили альтернативу под названием const assertion.

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

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

Спасибо за прочтение, я поговорю с вами в моей следующей статье.

Ваше здоровье :)