Как задать тип для всех значений объекта в TypeScript, кроме ключей?
Используйте Record с конкретным union-типом ключей
Вы почти у цели. Проблема в том, что Record<string, number> — это слишком широкая сигнатура. Она говорит TypeScript: "любая строка — это ключ, и значение всегда число". Но автодополнение для конкретных ключей теряется, потому что TypeScript не знает, какие именно строки существуют.
Решение — создать union-тип из точных ключей и использовать его как параметр для Record. Вот как это делается:
type FruitKeys = 'apple' | 'banana' | 'cherry';
const myDict: Record<FruitKeys, number> = {
apple: 5,
banana: 3,
cherry: 8
};
// Теперь myDict.apple — это number, и автодополнение работает!
// myDict.orange — ошибка компиляции
Если вы не хотите дублировать ключи в типе, можно использовать typeof и keyof:
const myDict = {
apple: 5,
banana: 3,
cherry: 8
} as const; // as const — важно для получения literal-типов ключей
// Теперь myDict — это { readonly apple: 5; readonly banana: 3; readonly cherry: 8 }
// Но мы хотим, чтобы значения были number, а не literal-типами.
// Используем вспомогательный тип:
type DictOfNumbers<T> = {
[K in keyof T]: number
};
const myDict2: DictOfNumbers<typeof myDict> = {
apple: 5,
banana: 3,
cherry: 8
};
// myDict2.apple — number, автодополнение есть!
Почему это работает?
Ключевая идея — mapped type (отображённый тип). Когда вы пишете Record<K, V>, это эквивалентно { [P in K]: V }. Если K — это union строк, то TypeScript создаёт объект с точными ключами. Если K — это string, то получается индексная сигнатура без конкретных ключей.
Union-тип 'apple' | 'banana' | 'cherry' — это не string, а подмножество строк. Поэтому TypeScript сохраняет автодополнение.
Дополнительные советы
- Если ключей много, используйте
as constдля объекта-константы иkeyof typeofдля автоматического получения union-типа:
const FRUITS = {
apple: 5,
banana: 3,
cherry: 8
} as const;
type FruitKeys = keyof typeof FRUITS; // 'apple' | 'banana' | 'cherry'
const myDict: Record<FruitKeys, number> = { ...FRUITS };
- Если нужно, чтобы объект был изменяемым (mutable), уберите
as constили используйте-readonlyв mapped type:
type MutableDict<T> = {
-readonly [K in keyof T]: number
};
Теперь вы можете менять значения, но тип значений остаётся number:
const myDict3: MutableDict<typeof FRUITS> = { apple: 5, banana: 3, cherry: 8 };
myDict3.apple = 10; // OK
// myDict3.apple = "text"; // Ошибка: Type 'string' is not assignable to type 'number'
Этот подход даёт вам и строгую типизацию значений, и полное автодополнение по ключам. Идеально для словарей, конфигураций и любых объектов с фиксированным набором ключей.
<<Альтернативный подход: интерфейс с индексной сигнатурой и конкретными ключами
Можно объединить индексную сигнатуру (для проверки всех значений) с конкретными ключами (для автодополнения). Но есть нюанс: TypeScript не позволяет одновременно иметь индексную сигнатуру и конкретные обязательные ключи, если индексная сигнатура шире. Однако можно сделать ключи опциональными:
interface MyDict {
[key: string]: number;
apple?: number; // опционально, но TypeScript запомнит
banana?: number;
cherry?: number;
}
const myDict: MyDict = {
apple: 5,
banana: 3,
cherry: 8
};
// myDict.apple — number | undefined (из-за опциональности)
// Но автодополнение работает!
Минус: все значения становятся number | undefined. Чтобы избежать этого, можно использовать строгий mapped type, как в первом ответе. Этот способ подходит, если ключи могут добавляться динамически, но вы хотите, чтобы IDE подсказывала основные.
Ещё один вариант — использовать const assertion и satisfies (TypeScript 4.9+):
const myDict = {
apple: 5,
banana: 3,
cherry: 8
} satisfies Record<string, number>;
// myDict.apple — number, автодополнение есть
// myDict.orange — ошибка: Property 'orange' does not exist
satisfies проверяет, что объект соответствует типу Record<string, number>, но не расширяет его. Поэтому TypeScript сохраняет точные ключи и выводит тип значений как number (а не number | undefined). Это самый лаконичный способ, если у вас TypeScript 4.9 или новее.
Ответов пока нет.
Войдите чтобы оставить ответ