← Назад к вопросам
Как использовать оператор Keyof для объекта?
1.8 Middle🔥 191 комментариев
#TypeScript
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Оператор Keyof в TypeScript
Keyof — это мощный оператор типов в TypeScript, который извлекает все ключи (свойства) из типа объекта. Это критично для создания типобезопасного и гибкого кода.
Что такое Keyof
Keyof создаёт тип, содержащий все возможные ключи объекта:
// Пример с интерфейсом
interface User {
id: number;
name: string;
email: string;
age: number;
}
// keyof User создаёт тип: 'id' | 'name' | 'email' | 'age'
type UserKeys = keyof User;
// Это эквивалентно:
type UserKeys = 'id' | 'name' | 'email' | 'age';
// Можно использовать эту переменную как тип
const key: UserKeys = 'name'; // OK
const key2: UserKeys = 'invalid'; // ERROR - 'invalid' не в типе
Основные примеры использования
1. Типизация функции с динамическими ключами
interface Product {
id: string;
title: string;
price: number;
inStock: boolean;
}
// Функция, которая может получить любой ключ Product
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]; // T[K] — это тип значения по ключу
}
const product: Product = {
id: '123',
title: 'Ноутбук',
price: 50000,
inStock: true
};
const id = getProperty(product, 'id'); // Тип: string
const price = getProperty(product, 'price'); // Тип: number
const name = getProperty(product, 'name'); // ERROR - 'name' не существует в Product
2. Типизированное обновление объекта
function updateProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): T {
return {
...obj,
[key]: value
};
}
const user: User = { id: 1, name: 'Иван', email: 'ivan@example.com', age: 25 };
const updated1 = updateProperty(user, 'name', 'Петр'); // OK, value должен быть string
const updated2 = updateProperty(user, 'age', 30); // OK, value должен быть number
const updated3 = updateProperty(user, 'age', 'тридцать'); // ERROR - число ожидается
const updated4 = updateProperty(user, 'invalid', 'value'); // ERROR - ключ не существует
3. Создание типизированного объекта-маппера
interface Colors {
primary: string;
secondary: string;
danger: string;
success: string;
}
// Объект, который должен иметь ВСЕ ключи из Colors
const colorMap: Record<keyof Colors, string> = {
primary: '#007bff',
secondary: '#6c757d',
danger: '#dc3545',
success: '#28a745'
// Если забыть какой-то ключ, TypeScript выдаст ошибку
};
// Более читаемый вариант
const colorMap2: { [K in keyof Colors]: string } = {
primary: '#007bff',
secondary: '#6c757d',
danger: '#dc3545',
success: '#28a745'
};
4. Условные типы с Keyof
interface Account {
email: string;
password: string;
createdAt: Date;
}
// Тип для получения только строковых полей
type StringFieldsOf<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
type AccountStringFields = StringFieldsOf<Account>;
// Результат: 'email' | 'password' (не 'createdAt')
// Пример использования
function validateStringField<T, K extends StringFieldsOf<T>>(
obj: T,
field: K,
value: string
) {
return { ...obj, [field]: value };
}
5. Типизированный Object.keys и Object.entries
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
} as const; // 'as const' для точных типов
type ConfigKey = keyof typeof config;
// ConfigKey = 'apiUrl' | 'timeout' | 'retries'
// Теперь Object.keys правильно типизирован
const keys: ConfigKey[] = Object.keys(config) as ConfigKey[];
// Типизированный переход по свойствам
Object.entries(config).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
6. Создание сигнатур для React компонентов
interface FormData {
username: string;
password: string;
rememberMe: boolean;
}
// Типизированный обработчик изменения поля формы
type FormHandler = <K extends keyof FormData>(
field: K,
value: FormData[K]
) => void;
const handleChange: FormHandler = (field, value) => {
// field и value всегда совпадают по типу
console.log(`${field}: ${value}`);
};
handleChange('username', 'john'); // OK
handleChange('rememberMe', true); // OK
handleChange('rememberMe', 'false'); // ERROR - ожидается boolean
7. Типизация глубоких операций с объектами
interface API {
users: { list: (id: string) => Promise<User[]>; delete: (id: string) => void };
posts: { list: () => Promise<Post[]>; create: (data: PostData) => void };
}
// Получить все методы из API
type APIMethods = keyof API;
// APIMethods = 'users' | 'posts'
// Получить методы конкретного сервиса
type UserMethods = keyof API['users'];
// UserMethods = 'list' | 'delete'
// Создать типизированный клиент
class APIClient {
async call<Service extends keyof API, Method extends keyof API[Service]>(
service: Service,
method: Method,
...args: any[]
) {
// Реализация
}
}
8. Типизация HOC и декораторов
function withFormValidation<T extends object>(
Component: React.ComponentType<T>
) {
return (props: T) => {
const [errors, setErrors] = React.useState<Partial<Record<keyof T, string>>>({});
return <Component {...props} errors={errors} />;
};
}
// Использование
interface LoginProps {
username: string;
password: string;
}
const LoginForm = (props: LoginProps) => <div>Form</div>;
const ProtectedLoginForm = withFormValidation(LoginForm);
Практический пример: Типизированный хук для формы
function useForm<T extends Record<string, any>>(initialValues: T) {
const [values, setValues] = React.useState(initialValues);
const handleChange = <K extends keyof T>(field: K, value: T[K]) => {
setValues(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = async (onSubmit: (values: T) => Promise<void>) => {
await onSubmit(values);
};
return { values, handleChange, handleSubmit };
}
// Использование
interface LoginForm {
email: string;
password: string;
rememberMe: boolean;
}
const form = useForm<LoginForm>({
email: '',
password: '',
rememberMe: false
});
form.handleChange('email', 'user@example.com'); // OK
form.handleChange('email', 123); // ERROR - number не совместим
form.handleChange('invalidField', 'value'); // ERROR - поля не существует
Ошибки и как их избежать
// ОШИБКА 1: Забыл keyof
function bad<T>(obj: T, key: string) {
return obj[key]; // Type is 'any' - нет типизации!
}
// ПРАВИЛЬНО
function good<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// ОШИБКА 2: Неправильный тип параметра
function update<T, K extends keyof T>(
obj: T,
key: K,
value: string // Слишком ограничиваем!
): void {}
// ПРАВИЛЬНО
function update<T, K extends keyof T>(
obj: T,
key: K,
value: T[K] // Точный тип для значения
): void {}
Сравнение с Object.keys
const obj = { a: 1, b: 'text', c: true };
// Object.keys возвращает string[]
const keys1 = Object.keys(obj); // Type: string[]
// С keyof можно получить точный тип
type Keys = keyof typeof obj;
const keys2: Keys[] = ['a', 'b', 'c']; // Type: ('a' | 'b' | 'c')[]
Oператор keyof — это фундаментальный инструмент для создания типобезопасного TypeScript кода. Он позволяет избежать ошибок на этапе разработки и делает код более предсказуемым и поддерживаемым.