Перейти к содержанию

Литеральные типы Number, String, Boolean, Unique Symbol, Enum

В TypeScript существуют примитивные типы данных, которые называются литеральными типами данных. Как можно понять из названия, типы выражаются литералами значений примитивных типов. Так например число 5, строка “apple”, логическое значение true или константа перечисления Fruits.Apple может выступать в качестве типа данных. Не сложно догадаться, что в качестве значений в таком случае могут выступать только литеральные эквиваленты самих типов, а также Null и Undefined (при strictNullChecks = false).

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

Литеральный тип Number

Литеральный тип Number должен состоять из литеральных значений, входящих в допустимый диапазон чисел, от Number.MIN_VALUE (-9007199254740992) до Number.MAX_VALUE (9007199254740992) и может записываться в любой системе счисления (двоичной, восьмеричной, десятичной, шестнадцатеричной).

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

const port80: number = 80
const port42: number = 42

// параметры ограничены лишь типом данных
function start(port: number): void {
  // блок if сообщит об ошибке только во время выполнения
  if (port !== port80 || port !== port42) {
    throw new Error(`port #${port} is not valid.`)
  }
}

start(81) // вызов с неправильным значением

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

const port80: number = 80
const port42: number = 42

function start(port: 80 | 42): void {
  // блок if сообщит об ошибке только во время выполнения
  if (port !== port80 || port !== port42) {
    throw new Error(`port #${port} is not valid.`)
  }
}

start(81) // ошибка выявлена на этапе компиляции!

Для повышения семантики кода создадим псевдоним типа для литерального типа.

type ValidPortValue = 80 | 42

const port80: number = 80
const port42: number = 42

function start(port: ValidPortValue): void {
  // блок if сообщит об ошибке только во время выполнения
  if (port !== port80 || port !== port42) {
    throw new Error(`port #${port} is not valid.`)
  }
}

start(81) // ошибка выявлена на этапе компиляции!

Как уже было сказано ранее, литеральный тип Number можно указывать в любой системе счисления.

type NumberLiteralType = 0b101 | 0o5 | 5 | 0x5

Примитивный литеральный тип Number является уникальным для TypeScript, в JavaScript подобного типа не существует.

Литеральный тип String

Литеральный тип String может быть указан только строковыми литералами, заключенными в одинарные ( ' ' ) или двойные ( " " ) кавычки. Так называемые шаблонные строки, которые заключены в обратные кавычки ( ` ` ), не могут быть использованы в качестве строкового литерального типа.

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

function animate(name: 'ease-in' | 'ease-out'): void {}

animate('ease-in') // Ok
animate('ease-in-out') // Error

Примитивный литеральный тип string является уникальным для TypeScript, в JavaScript подобного типа не существует.

Литеральный тип Boolean

Литеральный тип Boolean ограничен всего двумя литеральными значениями true и false.

Так как литеральный тип Boolean, также, как и обычный тип Boolean, состоит всего из двух литеральных значений true и false, то детально разбирать, собственно, и нечего.

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

function setFlag(flag: true | 'true'): void {}

Примитивный литеральный тип boolean является уникальным для TypeScript, в JavaScript подобного типа не существует.

Литеральный Тип Unique Symbol уникальный символьный тип

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

function f(key: symbol) {
  // для успешного выполнения программы предполагается, что параметр key будет принадлежать к типу Symbol.for('key')...
}

f(Symbol.for('bad key')) // ... тем не менее функцию f() можно вызвать с любым другим символом

Для того, чтобы избежать подобного сценария, TypeScript добавил новый примитивный литеральный тип данных Unique Symbol. Unique Symbol является подтипом Symbol и указывается в аннотации с помощью литерального представления unique symbol.

Создается unique symbol теми же способами, что и Symbol, с помощью прямого вызова конструктора Symbol() или с помощью метода класса Symbol.for(). Но, в отличие от symbol, unique symbol может быть указан только в аннотации константы (const) и поля класса (static), объявленного с модификатором readonly.

const v0: unique symbol = Symbol.for('key') // Ok
let v1: unique symbol = Symbol.for('key') // Error
var v2: unique symbol = Symbol.for('key') // Error

class Identifier {
  public static readonly f0: unique symbol = Symbol.for('key') // Ok

  public static f1: unique symbol = Symbol.for('key') // Error
  public f2: unique symbol = Symbol.for('key') // Error
}

Кроме того, чтобы ограничить значение до значения принадлежащего к типу unique symbol, требуется прибегать к механизму запроса типа, который подробно рассматривается в главе “Типы - Type Queries (запросы типа), Alias (псевдонимы типа)”.

const KEY: unique symbol = Symbol.for('key')

// аннотация параметра и возвращаемого из функции типа при помощи механизма запросов типа
function f(key: typeof KEY): typeof KEY {
  return key
}

f(KEY) // Ok
f(Symbol('key')) // Error
f(Symbol.for('key')) // Error

Поскольку каждый unique symbol имеет собственное представление в системе типов, то совместимыми могут считаться только символы, имеющие идентичную ссылку на объявление.

const KEY: unique symbol = Symbol.for('key')
const OTHER_KEY: unique symbol = Symbol.for('key')

if (KEY === OTHER_KEY) {
  // Ошибка, unique symbol не равно unique symbol
}

function f(key: typeof KEY): typeof KEY {
  return key
}

let key = KEY // let key: symbol; // symbol !== unique symbol

f(key) // Error

Тип unique symbol предназначен для аннотирования уникальных символьных литералов. С его помощью реализуется задуманное для JavaScript поведение в типизированной среде TypeScript.

Литеральный тип Enum

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

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

enum Berrys {
  Strawberry = 'strawberry',
  Raspberry = 'raspberry',
  Blueberry = 'blueberry',
}

type RedBerry = Berrys.Raspberry | Berrys.Strawberry

var berry: RedBerry = Berrys.Strawberry // Ok
var berry: RedBerry = Berrys.Raspberry // Ok
var berry: RedBerry = Berrys.Blueberry // Error
var berry: RedBerry = 123 // Error
var berry: RedBerry = 'strawberry' // Error

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

enum Fruits {
  Apple,
  Pear,
  Banana = 'banana',
}

type FruitGrowOnTree = Fruits.Apple | Fruits.Pear

var fruit: FruitGrowOnTree = Fruits.Apple // Ok
var fruit: FruitGrowOnTree = Fruits.Pear // Ok
var fruit: FruitGrowOnTree = Fruits.Banana // Error
var fruit: FruitGrowOnTree = 123 // Ok!
var fruit: FruitGrowOnTree = 'apple' // Error

Правила литеральных типов Enum распространяются и на перечисление, объявленное с помощью ключевого слова const.

Примитивный литеральный тип Enum является уникальным для TypeScript, в JavaScript подобного типа не существует.

Итоги

  • Литеральный тип Number должен указываться числовыми литералами, входящими в допустимый диапазон от Number.MIN_VALUE до Number.MAX_VALUE и может записываться в двоичной, восьмеричной, десятичной и шестнадцатеричной системе счисления.
  • Литеральный тип String должен указываться литералами строк, заключенными в одинарные (' ') или двойные кавычки (" ").
  • Литеральные тип Boolean указывается при помощи литеральных значений true и false.
  • Литеральный тип Enum указывается с помощью констант, объявленных в перечислении.
  • На литеральный тип Enum действуют те же правила сопоставления типов, что и на обычный Enum.