TypeScript / 39 минут
keyof и индексный доступ
Свяжите ключ объекта с типом его значения без потери информации.
- Свяжите ключ объекта с типом его значения без потери информации.
- Применить тему в задаче «keyof и индексный доступ» и объяснить граничные случаи.
Около 9 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.
Ключи как часть статического контракта
Когда функция читает поле объекта по переданному имени, хочется связать допустимые имена с реальной формой объекта. Произвольный string слишком широк: он разрешает ключ, которого в модели нет. keyof T извлекает союз ключей типа T и позволяет выразить такую связь. Это операция над типами, она не перечисляет runtime-свойства объекта автоматически.
Для формы {name:string,age:number} результат keyof — 'name' | 'age'. Это не string | number из типов значений. Полезно каждый раз проговаривать, на какой вопрос отвечает выражение: «какие имена разрешены» или «какие значения лежат по этим именам». Смешение этих двух уровней часто приводит к ненужным any и неверным утверждениям.
type User = { name: string; age: number };
type UserKey = keyof User;
const key: UserKey = 'name';
// const invalid: UserKey = 'password'; // ошибкаT[K] и точный результат чтения
Индексный доступ к типу T[K] извлекает тип значения по ключу. Если K является союзом нескольких ключей, результатом может стать соответствующий союз типов значений. Он не выполняет runtime-доступ сам по себе. Реальный object[key] должен присутствовать в функции отдельно и соответствовать объявленной связи.
type Name = User['name']; // string
type PublicValue = User[keyof User]; // string | number
function property<T, K extends keyof T>(object: T, key: K): T[K] {
return object[key];
}Ограничение K extends keyof T запрещает произвольное имя. Возврат T[K] сохраняет связь конкретного ключа с результатом. Если заменить результат на any, вы потеряете главную пользу обобщения. Если всегда возвращать string | number, обобщённая функция перестанет подходить объектам с другими видами значений.
typeof в позиции типа
В TypeScript typeof может использоваться для получения статического типа уже объявленного значения. Это помогает избежать дублирования конфигурации: сначала создаётся runtime-объект, затем из его типа выводят ключи и значения. Не путайте такую запись с JavaScript-проверкой typeof value, которая выполняется в программе и возвращает строку.
const routes = { home: '/', courses: '/courses' } as const;
type RouteName = keyof typeof routes;
type RoutePath = typeof routes[RouteName];as const сохраняет узкие литеральные значения, поэтому RoutePath здесь описывает конкретные пути, а не любую строку. Это полезно для внутренней конфигурации. Для внешнего URL всё равно требуется проверка: компилятор не может доказать, что произвольная строка адресной строки относится к закрытому набору разрешённых маршрутов.
Почему Object.keys возвращает не идеальный keyof[]
Структурный тип не всегда описывает полный закрытый набор runtime-свойств. Объект, переданный как User, может фактически иметь дополнительные поля. Поэтому безусловное обещание, что Object.keys(value) перечислит только keyof User, могло бы быть неверным. Статическая совместимость формы и точное перечисление реального объекта — разные гарантии.
const detailed = { name: 'Анна', age: 20, internal: true };
const user: User = detailed;
console.log(Object.keys(user)); // internal тоже существует в runtimeНе решайте каждый такой случай утверждением as (keyof T)[] без анализа. Иногда нужен явный список разрешённых ключей, иногда собственная проверка схемы, иногда более подходящий контейнер Map. Утверждение типа допустимо лишь там, где у вас есть независимое основание для заявленной гарантии, а не просто желание убрать сообщение компилятора.
Динамические ключи и необязательные значения
Если тип имеет индексную сигнатуру, keyof может включать более широкий набор ключей. При optional-поле чтение T[K] также учитывает возможное отсутствие. Это честное описание: допустимость имени не доказывает наличие необязательного свойства у конкретного объекта. Для обычного словаря произвольных ключей полезно рассмотреть noUncheckedIndexedAccess и явный результат отсутствия.
type Settings = { title?: string; limit: number };
type Title = Settings['title']; // string | undefined
function title(value: Settings): string {
return value.title ?? 'Без названия';
}Разделяйте проверку разрешённого имени и проверку значения. Например, пользователь может выбрать разрешённый столбец, но конкретная запись не иметь optional-данных для него. Интерфейс должен согласованно показать отсутствие, а не случайно преобразовать undefined в видимую подпись.
Практическое чтение свойства
В задании generic helper get связывает T, K и T[K], а solve использует его для name или age. Числовой возраст должен остаться числом, включая ноль; пустое имя должно остаться строкой. Преобразование всех результатов в строку нарушит контракт, хотя визуальный вывод в консоли может выглядеть похожим.
Проверьте оба ключа на нескольких объектах. Затем попробуйте передать 'email' и убедитесь, что ошибка возникает при статической проверке. Для самостоятельной тренировки опишите функцию обновления свойства с третьим параметром value:T[K]. Объясните, почему такая сигнатура не позволит записать строку в age, если конкретный K известен как 'age'.