Is there a `valueof` similar to `keyof` in TypeScript? Оптимизация и производительность
Вы когда-нибудь писали на TypeScript и ловили себя на мысли: «У меня есть keyof для ключей, а где же valueof для значений?» Если да — вы не одиноки. Этот вопрос на Stack Overflow собрал тысячи просмотров, и сегодня мы разберём его от и до. Но не просто как абстрактную теорию, а с фокусом на оптимизацию и производительность вашего кода. Погнали!
Почему `keyof` есть, а `valueof` — нет?
В TypeScript есть встроенный утилитарный тип keyof. Он берёт объектный тип и возвращает объединение всех его ключей:
type Person = {
name: string;
age: number;
isActive: boolean;
};
type PersonKeys = keyof Person; // "name" | "age" | "isActive"
А что насчёт значений? Хочется получить string | number | boolean из того же типа. В стандартной библиотеке TypeScript такого нет. Почему? Потому что valueof — это не просто «обратная операция». У объекта может быть много ключей с одинаковыми типами, и нам нужно решить, как это объединять. Но это не значит, что мы не можем написать свой valueof!
Создаём свой `ValueOf`
Самый простой способ — использовать индексный тип доступа (indexed access type) в сочетании с keyof:
type ValueOf<T> = T[keyof T];
type PersonValues = ValueOf<Person>; // string | number | boolean
Вот и всё! Мы только что создали дженерик-тип ValueOf, который работает как valueof. Но давайте копнём глубже — как это влияет на производительность и как использовать с умом.
Оптимизация: когда `ValueOf` спасает от дублирования
Представьте, что у вас есть конфигурационный объект с десятками полей. Вы хотите написать функцию, которая принимает любое значение из этого объекта. Без ValueOf вы бы перечисляли типы вручную:
type Config = {
apiUrl: string;
timeout: number;
retries: number;
debug: boolean;
};
// Плохо: дублирование типов
function setConfigValue(key: string, value: string | number | boolean) {
// ...
}
А теперь с ValueOf:
// Хорошо: автоматически подхватывает все типы значений
function setConfigValue(key: keyof Config, value: ValueOf<Config>) {
// TypeScript знает, что value может быть только string | number | boolean
}
Это не только сокращает код, но и делает его самодокументируемым. Если вы добавите новое поле в Config, TypeScript подхватит новый тип автоматически. Меньше ошибок — больше производительности разработки.
Продвинутый `ValueOf` с условными типами
Иногда нужно получить значения только определённых ключей. Например, все числовые значения. Тут на помощь приходят условные типы (conditional types):
type NumericValues<T> = {
[K in keyof T]: T[K] extends number ? T[K] : never;
}[keyof T];
type ConfigNumeric = NumericValues<Config>; // number (timeout | retries)
Этот приём особенно полезен при работе с большими типами: вы можете отфильтровать только нужные значения и избежать лишних проверок в рантайме. Оптимизация на уровне типов — это бесплатная производительность, потому что типы исчезают при компиляции.
Производительность в рантайме: а нужно ли?
Важный момент: ValueOf — это чисто типовая конструкция. Она не генерирует никакого JavaScript-кода. Но её правильное использование может косвенно повысить производительность вашего приложения:
- Меньше багов — меньше времени на отладку.
- Более строгая типизация — компилятор ловит ошибки до рантайма.
- Легче рефакторить — изменения в одном месте не ломают всё.
Однако есть и подводные камни. Если вы используете ValueOf с очень большими объединениями типов (например, объект с 100+ полями), компилятор может замедлиться. Но это случается редко и обычно решается разбиением типов на более мелкие.
Реальный кейс: валидация данных
Допустим, вы пишете валидатор для формы. У вас есть тип полей и их допустимых значений. С ValueOf вы можете создать универсальную функцию:
type FormFields = {
email: string;
age: number;
subscribe: boolean;
};
function validateField<K extends keyof FormFields>(
field: K,
value: ValueOf<Pick<FormFields, K>>
): boolean {
// TypeScript знает, что value — это именно тот тип, который соответствует ключу
switch (field) {
case 'email':
return typeof value === 'string' && value.includes('@');
case 'age':
return typeof value === 'number' && value > 0;
case 'subscribe':
return typeof value === 'boolean';
default:
return false;
}
}
// Использование
validateField('email', 'test@example.com'); // OK
validateField('age', 25); // OK
validateField('email', 123); // Ошибка на этапе компиляции!
Обратите внимание на Pick<FormFields, K> — мы сужаем тип до одного поля, чтобы ValueOf вернул точный тип, а не объединение всех. Это ключевая оптимизация, которая делает код и безопасным, и производительным.
Сравнение с другими подходами
Иногда разработчики пытаются имитировать valueof через перечисляемые типы (enums) или константы. Сравним:
| Подход | Плюсы | Минусы |
|---|---|---|
| Enum | Простота, рантайм-значения | Не гибкий, сложно расширять |
| ValueOf (наш) | Гибкость, автоматическое обновление |
БлогиРекомендаций по книгамЧитайте также |