Avatar ·

Что означает "keyof typeof" в TypeScript и как это работает?

Всем привет! Я начал изучать TypeScript недавно и столкнулся с конструкцией `keyof typeof`, которая встречается в некоторых примерах кода. Я понимаю, что `keyof` используется для получения ключей интерфейса или типа, а `typeof` — для получения типа переменной. Но когда их объединяют вместе, я теряюсь. Например, у меня есть такой код: ```typescript const colors = { red: '#ff0000', green: '#00ff00', blue: '#0000ff' }; type ColorKeys = keyof typeof colors; ``` Что здесь происходит? Почему нельзя просто написать `keyof colors`? И что именно получается в итоге? Я пробовал использовать это в функции, которая принимает ключ объекта, но TypeScript ругается на типы. Можете объяснить простыми словами, как работает связка `keyof typeof` и в каких ситуациях её лучше применять? Заранее спасибо за помощь! <<>>

Что такое keyof typeof и как это работает

keyof typeof — это комбинация двух операторов TypeScript, которая позволяет получить тип, представляющий все ключи существующего объекта (а не интерфейса или типа). Давайте разберём каждый оператор по отдельности, а затем посмотрим, как они работают вместе.

Оператор typeof в контексте типов

В TypeScript typeof, используемый в позиции типа (после двоеточия или в угловых скобках), возвращает тип переменной. Например:

const name = "Alice";
type NameType = typeof name; // string

const age = 30;
type AgeType = typeof age; // number

const user = { id: 1, name: "Bob" };
type UserType = typeof user; // { id: number; name: string; }

Обратите внимание: typeof user даёт не просто "object", а точную структуру объекта со всеми типами полей.

Оператор keyof

keyof берёт тип и возвращает объединение (union) всех его ключей. Например:

interface Person {
  name: string;
  age: number;
  email: string;
}

type PersonKeys = keyof Person; // "name" | "age" | "email"

Почему нельзя просто keyof colors?

Потому что keyof работает только с типами, а не со значениями (переменными, константами). colors — это значение (константа), а не тип. Если написать keyof colors, TypeScript выдаст ошибку: 'colors' refers to a value, but is being used as a type here.

Как работает keyof typeof

Конструкция keyof typeof colors работает в два этапа:

  1. typeof colors — получает тип объекта colors: { red: string; green: string; blue: string; }
  2. keyof typeof colors — берёт ключи этого типа: "red" | "green" | "blue"

В результате ColorKeys будет равен типу "red" | "green" | "blue".

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

Допустим, вы хотите написать функцию, которая принимает ключ объекта colors и возвращает соответствующий цвет:

const colors = {
  red: '#ff0000',
  green: '#00ff00',
  blue: '#0000ff'
};

type ColorKeys = keyof typeof colors; // "red" | "green" | "blue"

function getColor(key: ColorKeys): string {
  return colors[key];
}

// Использование
getColor('red');   // OK
getColor('green'); // OK
getColor('yellow'); // Ошибка! Argument of type '"yellow"' is not assignable to parameter of type '"red" | "green" | "blue"'

Без keyof typeof пришлось бы вручную объявлять тип "red" | "green" | "blue", что неудобно и подвержено ошибкам при изменении объекта.

Когда использовать keyof typeof

  • Работа с конфигурациями и константами — когда у вас есть объект с фиксированными ключами, и вы хотите безопасно обращаться к его свойствам.
  • Создание типов на основе существующих объектов — например, для динамических форм, где ключи объекта определяют поля формы.
  • Enum-подобные структуры — когда вы используете объект как альтернативу enum (например, для строковых значений).

Важный нюанс

keyof typeof работает только с объектами, определёнными через const или let. Для интерфейсов и типов (которые и так являются типами) достаточно просто keyof.

interface Config {
  apiUrl: string;
  timeout: number;
}

type ConfigKeys = keyof Config; // "apiUrl" | "timeout"
// Здесь typeof не нужен, так как Config — уже тип
<<>>

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

Если вы хотите получить не только ключи, но и точные литеральные типы значений (а не просто string), используйте as const вместе с keyof typeof:

const colors = {
  red: '#ff0000',
  green: '#00ff00',
  blue: '#0000ff'
} as const;

type ColorKeys = keyof typeof colors; // "red" | "green" | "blue"
type ColorValues = typeof colors[ColorKeys]; // "#ff0000" | "#00ff00" | "#0000ff"

Без as const тип ColorValues был бы просто string, что менее точно. С as const вы получаете конкретные литеральные строки.

Комбинация с Record

keyof typeof часто используется для создания типов на основе существующих объектов:

const statuses = {
  active: 'Активен',
  inactive: 'Неактивен',
  pending: 'В ожидании'
} as const;

type StatusKey = keyof typeof statuses; // "active" | "inactive" | "pending"
type StatusMap = Record<StatusKey, string>; // { active: string; inactive: string; pending: string; }

// Теперь можно безопасно работать с объектами
function getStatusLabel(status: StatusKey): string {
  return statuses[status];
}

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