Аналог `valueof` для `keyof` в TypeScript: как получить тип значений объекта?
Решение: утилитарный тип `ValueOf`
Да, в TypeScript нет встроенного аналога keyof для значений, но его очень легко создать самостоятельно. Основная идея — использовать индексированный доступ к типу через T[keyof T]. Это выражение означает: «возьми все возможные значения свойств объекта T и объедини их в union».
Вот как выглядит базовая реализация:
type ValueOf<T> = T[keyof T];
Теперь применим её к вашему примеру:
const config = {
host: 'localhost',
port: 3000,
debug: true
} as const;
type ConfigValues = ValueOf<typeof config>;
// Результат: string | number | boolean
Обратите внимание на as const. Без него TypeScript вывел бы более широкие типы (string, number, boolean), а с as const — конкретные литералы ('localhost', 3000, true). Но ValueOf работает в обоих случаях.
Расширенная версия для вложенных объектов
Если вам нужно получать типы значений из вложенных структур, можно сделать рекурсивную версию:
type DeepValueOf<T> = T extends object
? { [K in keyof T]: DeepValueOf<T[K]> }[keyof T]
: T;
interface NestedConfig {
server: {
host: string;
port: number;
};
features: {
darkMode: boolean;
analytics: boolean;
};
}
type AllValues = DeepValueOf<NestedConfig>;
// Результат: string | number | boolean
Практический пример с функцией
Вернёмся к вашей задаче с функцией, которая принимает ключ и возвращает значение правильного типа:
function getConfigValue<K extends keyof typeof config>(key: K): ValueOf<typeof config> {
return config[key];
}
// Использование
const host = getConfigValue('host'); // тип: string | number | boolean
Обратите внимание: возвращаемый тип всё ещё string | number | boolean, а не конкретный тип для конкретного ключа. Чтобы сделать более точную типизацию, используйте индексированный доступ напрямую:
function getConfigValue<K extends keyof typeof config>(key: K): typeof config[K] {
return config[key];
}
const host = getConfigValue('host'); // тип: "localhost"
const port = getConfigValue('port'); // тип: 3000
Важные замечания
- Производительность:
ValueOfне влияет на рантайм — это чисто компиляторная конструкция. - Ограничения: Если объект содержит методы (функции), они тоже попадут в union. Чтобы исключить их, можно добавить фильтрацию:
type ValueOfNonFunction<T> = {
[K in keyof T]: T[K] extends Function ? never : T[K];
}[keyof T];
Этот подход полностью покрывает потребности, для которых мог бы быть нужен гипотетический valueof. Используйте его в своих проектах для повышения типобезопасности!
Альтернативный подход: использование условных типов
Если вам нужно получать значения только определённых типов (например, только строки или только числа), можно комбинировать ValueOf с условными типами:
type ValueOfType<T, V> = {
[K in keyof T]: T[K] extends V ? T[K] : never;
}[keyof T];
interface Mixed {
id: number;
name: string;
active: boolean;
price: number;
}
type StringValues = ValueOfType<Mixed, string>; // string
type NumberValues = ValueOfType<Mixed, number>; // number
Это полезно, когда нужно работать только с подмножеством значений объекта. Например, для валидации или фильтрации данных.
Для маппированных типов
Ещё один вариант — использовать infer с условными типами, если вам нужно получить тип значения по конкретному ключу:
type ValueForKey<T, K> = K extends keyof T ? T[K] : never;
type NameType = ValueForKey<Mixed, 'name'>; // string
Это более гибкий подход, чем жёстко заданный ValueOf, если вы знаете конкретные ключи, с которыми работаете. Однако для большинства задач базового ValueOf из первого ответа будет более чем достаточно.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Работа с файловой системой в Kotlin: полное руководство