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

Примитивные литеральные типы Number, String, Template String, Boolean, Unique Symbol, Enum

Помимо обычных примитивных типов, перешедших из JavaScript, в TypeScript существуют так называемые литеральные типы, которые, как можно понять из названия, представляют собой литералы обычных примитивных типов. Число 5, строка “apple”, логическое значение true или константа перечисления Fruits.Apple может выступать в качестве самостоятельного типа. Несложно догадаться, что в качестве значений в таком случае могут выступать только литеральные эквиваленты самих типов, а также null и undefined (при --strictNullChecks со значением false).

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

Литеральный тип Number (Numeric Literal Types)

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 стало возможно выявлять ошибки, не дожидаясь выполнения программы. В данном случае значение допустимых портов можно указать в качестве типа параметров функции.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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); // ошибка выявлена на этапе компиляции!

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 можно указывать в любой системе счисления.

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

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

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

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

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

1
2
3
4
function animate(name: 'ease-in' | 'ease-out'): void {}

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

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

Шаблонный литеральный тип String

Шаблонный литеральный строковый тип (Template String Literal Types) - это тип, позволяющий на основе литеральных строковых типов динамически определять новый литеральный строковый тип. Простыми словами, это известный по JavaScript механизм создания шаблонных строк только для типов.

1
2
3
4
5
6
7
type Type = "Type";
type Script = "Script";

/**
 * type Messge = "I ❤️ TypeScript"
 */
type Messge = `I ❤️ ${Type}${Script}`;

Но вся мощь данного типа раскрывается в момент определения нового типа на основе объединения (union). В подобных случаях новый тип будет также представлять объединение, элементы которого представляют все возможные варианты, полученные на основе исходного объединения.

1
2
3
4
5
6
type Sides = "top" | "right" | "bottom" | "left";

/**
 * type PaddingSides = "padding-top" | "padding-right" | "padding-bottom" | "padding-left"
 */
type PaddingSides = `padding-${Sides}`;

Аналогичное поведение будет справедливо и для нескольких типов объединения.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type AxisX = "top" | "bottom";
type AxisY = "left" | "right";


/**
 * type Sides = "top-left" | "top-right" | "bottom-left" | "bottom-right"
 */
type Sides = `${AxisX}-${AxisY}`;

/**
 * type BorderRadius = "border-top-left-radius" | "border-top-right-radius" | "border-bottom-left-radius" | "border-bottom-right-radius"
 */
type BorderRadius = `border-${Sides}-radius`;

Поскольку с высокой долей вероятности в подобных операциях потребуется трансформация регистра строк, создателями данного механизма также были добавлены новые операторы преобразования uppercase, lowercase, capitalize и uncapitalize. Данные операторы применяются непосредственно к литеральному строковому типу, который указывается справа от него.

1
2
3
4
type A = `${uppercase "AbCd"}`; // type A = "ABCD"
type B = `${lowercase "AbCd"}`; // type B = "abcd"
type C = `${capitalize "abcd"}`; // type C = "Abcd"
type D = `${uncapitalize "Abcd"}`; // type D = "abcd"

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

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

Так как литеральный тип boolean состоит всего из двух литеральных значений true и false, то детально разбирать, собственно, и нечего. Зато это прекрасный повод ещё раз повторить определение. Каждый раз, когда встречается часть кода, работа которой зависит от заранее определенного значения-константы, стоит подумать, можно ли ограничивать тип литеральным типом. Сможет ли это повысить типобезопасность и семантику кода.

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

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

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

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

1
2
3
4
5
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 (псевдонимы типа).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 имеет собственное представление в системе типов, то совместимыми могут считаться только символы, имеющие идентичную ссылку на объявление.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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 Literal Types) ограничивается литеральными значениями его констант. Это утверждение верно с одной оговоркой: правило совместимости типов для перечисления, у которого имеются константы с числовым значением, распространяется и на литеральный тип enum.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 подобного типа не существует.

Комментарии