Avatar ·

Как задать конкретную строку в интерфейсе TypeScript (литеральный тип)

Всем привет! Я только начал изучать TypeScript и столкнулся с проблемой. Мне нужно создать интерфейс для объекта, у которого есть поле `status`. Это поле должно принимать строго определённые значения, например: `"active"`, `"inactive"` или `"pending"`. Если попытаться присвоить любую другую строку, должна возникнуть ошибка компиляции. Пробовал сделать так: ```typescript interface UserStatus { status: string; } ``` Но это не подходит, потому что `string` разрешает любые строки. Я хочу, чтобы TS проверял, что значение поля `status` — одна из трёх возможных строк. Пытался использовать `enum`, но мне сказали, что можно сделать проще — с помощью литеральных типов. Как правильно описать такой интерфейс? И можно ли сделать так, чтобы IDE подсказывала возможные варианты при вводе? Пример того, что я хочу: ```typescript const user1: User = { status: "active" }; // Ок const user2: User = { status: "deleted" }; // Ошибка! ``` Подскажите, пожалуйста, как это реализовать. Заранее спасибо! <<>>

Решение: литеральные типы (Literal Types) в TypeScript

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

Базовое решение

Простой и понятный способ — объявить тип-объединение строковых литералов:

type Status = "active" | "inactive" | "pending";

interface User {
  name: string;
  status: Status;
}

// ✅ Корректное использование
const user1: User = { name: "Alice", status: "active" };

// ❌ Ошибка компиляции: Type '"deleted"' is not assignable to type 'Status'
const user2: User = { name: "Bob", status: "deleted" };

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

Прямое объявление в интерфейсе (без отдельного типа)

Можно указать литералы прямо в определении интерфейса, если не планируете переиспользовать тип:

interface User {
  name: string;
  status: "active" | "inactive" | "pending";
}

Как работают литеральные типы

  • Строковый литерал — это конкретная строка, например "active".
  • Объединение (|) — позволяет переменной принимать одно из перечисленных значений.
  • Если значение не входит в объединение, TypeScript выдаст ошибку на этапе компиляции.

Продвинутые возможности

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

// Числовые литералы
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

// Булевые литералы
type Switch = true | false; // эквивалентно boolean

// Комбинация
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

Автодополнение в IDE

При использовании литеральных типов современные редакторы (VS Code, WebStorm) показывают список возможных значений при наборе кода. Это очень удобно:

const user: User = { 
  name: "Alice", 
  status: // здесь IDE покажет подсказку: "active" | "inactive" | "pending"
};

Почему это лучше, чем enum

  • Проще и короче — не нужно создавать отдельную конструкцию.
  • Легче читается — сразу видно допустимые значения.
  • Совместимость с JSON — при сериализации/десериализации не возникает проблем.
  • Более гибко — можно комбинировать с другими типами.

Проверка типа в рантайме

Литеральные типы работают только на этапе компиляции. Если вам нужно проверять значение во время выполнения (например, при получении данных из API), используйте защитники типа (type guards):

function isValidStatus(value: string): value is Status {
  return ["active", "inactive", "pending"].includes(value);
}

// Использование
const input = "active";
if (isValidStatus(input)) {
  // здесь TypeScript знает, что input имеет тип Status
  console.log(input.toUpperCase()); // "ACTIVE"
}

Этот подход даёт и безопасность типов на этапе компиляции, и проверку во время выполнения.

<<>>

Альтернативный подход: использование const assertions

Если вам нужно определить набор строк, которые могут меняться в одном месте, можно использовать const assertions (as const) вместе с массивом:

const STATUSES = ["active", "inactive", "pending"] as const;
type Status = typeof STATUSES[number]; // "active" | "inactive" | "pending"

interface User {
  name: string;
  status: Status;
}

Этот подход полезен, когда:

  • У вас уже есть массив допустимых значений (например, для валидации).
  • Вы хотите избежать дублирования — массив и тип используют одни и те же данные.
  • Набор значений может расширяться — достаточно изменить массив, и тип обновится автоматически.

Как это работает

Ключевое слово as const делает массив readonly и выводит его элементы как литеральные типы, а не как string. Затем конструкция typeof STATUSES[number] извлекает объединение всех элементов массива.

Пример использования

function checkStatus(status: Status) {
  // status может быть только "active", "inactive" или "pending"
  if (STATUSES.includes(status)) {
    console.log("Valid status");
  }
}

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