Существует ли в TypeScript аналог `keyof` для получения типов значений объекта?
Прямого аналога нет, но есть простое решение
В TypeScript действительно нет встроенного оператора valueof, работающего как keyof. Однако вы можете легко создать свой условный тип, который будет извлекать все типы значений из объекта. Для этого используется синтаксис доступа по индексу (indexed access type) в сочетании с keyof.
Основной подход: T[keyof T]
Самый простой способ получить объединение значений объекта — написать Т[keyof T]. Это работает для любых типов, включая интерфейсы, типы и объекты, обернутые в typeof.
// Для вашего примера с конфигом
const appConfig = {
apiUrl: 'https://api.example.com',
port: 3000,
debug: true
};
// Создаем тип значения
type AppConfigValues = typeof appConfig[keyof typeof appConfig];
// Результат: string | number | boolean
// Пример использования функции, принимающей любое значение из конфига
function logConfigValue(value: AppConfigValues) {
console.log(value);
}
logConfigValue(appConfig.apiUrl); // OK
logConfigValue(appConfig.port); // OK
logConfigValue(appConfig.debug); // OK
// logConfigValue('custom'); // Ошибка, если нужно строго из конфига
Создание переиспользуемого типа ValueOf
Для удобства можно объявить generic-тип, который можно будет переиспользовать в проекте:
// Утилита для получения типов значений
type ValueOf<T> = T[keyof T];
// Теперь можно писать более читаемо
type AppConfigValues = ValueOf<typeof appConfig>; // string | number | boolean
// Работает с интерфейсами
interface User {
id: number;
name: string;
role: 'admin' | 'user';
}
type UserValues = ValueOf<User>; // number | string | 'admin' | 'user'
Важные нюансы и продвинутые варианты
- Работа с массивами: Для массивов
T[keyof T]вернет методы массива (length, push, pop и т.д.), а не элементы. Для получения типа элементов используйтеT[number]. - Фильтрация по типу ключа: Если нужно получить значения только для ключей определенного типа (например, только строковые значения), используйте условные типы:
type StringValues<T> = { [K in keyof T]: T[K] extends string ? T[K] : never }[keyof T]; type ConfigStringValues = StringValues<typeof appConfig>; // только 'https://api.example.com' - Глубокое извлечение: Для вложенных объектов можно использовать рекурсивные типы, но это выходит за рамки базового вопроса.
Почему нет встроенного valueof?
Разработчики TypeScript решили не добавлять отдельный оператор, потому что T[keyof T] — это идиоматическое и гибкое решение. Оно легко комбинируется с другими утилитами (Pick, Omit, Partial) и не требует новых синтаксических конструкций. К тому же, keyof возвращает тип, а valueof в некоторых языках (например, в Java) работает на уровне значений, что для TypeScript было бы неоднозначно.
Таким образом, используйте T[keyof T] или создайте тип-обертку ValueOf<T> — это стандартный и рекомендуемый способ в TypeScript сообществе.
Альтернативный подход: использование условных типов для точного контроля
Если вам нужно не просто получить все значения, а, например, исключить определенные типы (null, undefined) или работать с конкретными ключами, можно использовать более точные конструкции.
Пример: получение только не-null значений
type NonNullableValues<T> = {
[K in keyof T]: T[K] extends null | undefined ? never : T[K]
}[keyof T];
const data = {
a: 'hello',
b: null,
c: 42,
d: undefined
};
type CleanValues = NonNullableValues<typeof data>; // string | number
Пример: значения только для определенных ключей
Иногда нужно получить значения не для всех ключей, а только для тех, которые удовлетворяют определенному условию. Это можно сделать с помощью Pick или маппинга:
// Только значения для строковых ключей
type StringKeyValues<T> = {
[K in keyof T as K extends string ? K : never]: T[K]
}[keyof T];
// Или с использованием Pick
type PickedValues<T, K extends keyof T> = T[K];
// Пример:
type UserSubset = PickedValues<User, 'id' | 'name'>; // number | string
Этот подход особенно полезен, когда вы работаете с большими объектами и хотите ограничить область видимости типа. Хотя T[keyof T] — самое простое решение, условные типы дают больше контроля и гибкости в сложных сценариях.