← Назад к вопросам

Как использовать оператор 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 кода. Он позволяет избежать ошибок на этапе разработки и делает код более предсказуемым и поддерживаемым.

Как использовать оператор Keyof для объекта? | PrepBro