Реализация Pick в TypeScript
Задача этого типа состоит в том, чтобы создать свою собственную версию pick (разумеется, без медиатора), не используя какие-либо служебные типы. Также предоставим вам информацию о дополнительных типах утилит для манипуляции данными и типами.
Ссылка: https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md
Теги:
- Индексированные типы доступа/типы поиска
- Сопоставленные типы
- Общие ограничения
Объяснение:
Нам нужно создать новый тип объекта только со свойствами как в Type
, так и в Union
type MyPick<Type, Union> = /* ... */
Типы поиска
Чтобы получить тип ключей (ключи также известны как индексированные типы доступа или типы поиска) в Chair
, мы можем использовать обозначения в квадратных скобках точно так же, как доступ к свойству или методу в объекте JavaScript.
type Chair = {
color: string,
legCount: number,
isComfortable: boolean
}
Chair["color"] // string
Chair["legCount"] // number
Обратите внимание, что для индексации можно использовать только типы
const key = "color"
Chair[key] // Type "key" cannot be used as an index type
Сопоставленные типы
Как мы можем выполнить итерацию по объединению и создать новый тип объекта? Ответ заключается в сопоставлении типов.
Например, мы можем установить для каждого ключа Chair
значение boolean, подобное этому, с помощью динамических ключей и оператора in
.
type ChairKeysUnion = "color" | "legCount" | "isComfortable"
type BooleanChair = {
[Key in ChairKeysUnion]: boolean
}
/*
type BooleanChair = {
color: boolean;
legCount: boolean;
isComfortable: boolean;
}
*/
Обратите внимание, что Key
в этом примере является параметром, поэтому это просто имя-заполнитель. Это можно было бы назвать Property
или Jimothy
и работать с таким же успехом.
Общие ограничения
Но почему мы не можем просто объединить сопоставленные типы и типы поиска, чтобы присвоить каждому свойству в объединении соответствующий тип поиска, как это?
type MyPick<Type, Union> = {
[Key in Union]: Type[Key]
}
На данный момент MyPick
не является typesafe — он принимает любые свойства в Union
. В случае, если свойство не существует в Type
, переданном в MyPick
, оно будет в новом типе объекта со значением unknown
.
type NewChair = MyPick<Chair, 'color' | 'isComfortable' | 'invalid'>
/*
type NewChair = {
color: string;
isComfortable: boolean;
invalid: unknown;
}*/
keyof
Чтобы ограничить Union
только типами поиска в Type
, нам сначала понадобятся типы поиска Type
. Для этого мы можем использовать оператор keyof
. Он принимает тип объекта и возвращает строку, объединение строк или объединение чисел ключей типа.
type Chair = {
color: string,
legCount: number,
isComfortable: boolean
}
type ChairIndexUnion = keyof Chair // "color" | "legCount" | "isComfortable"
Теперь, когда у нас есть оба объединения, мы можем ограничить параметр Union
для поиска типов в Type
с помощью ключевого слова extends
.
type MyPick<Type, Union extends keyof Type> = {
[Key in Union]: Type[Key]
}
type NewChair = MyPick<Chair, 'color' | 'isComfortable' | 'invalid'>
/*
Type '"color" | "isComfortable" | "invalid"' does not satisfy the constraint 'keyof Chair'.
Type '"invalid"' is not assignable to type 'keyof Chair'.
*/
invalid
не находится в Chair
, поэтому тип NewChair
недопустим.
Теперь у вас есть типобезопасная функция Pick.