Нужно создать свой собственный тип MyPick<T, K>, который будет работать ровно так же, как встроенный Pick<T, K>. Что делает Pick?
- Он берет тип
T. - Он выбирает из него только те свойства, имена которых перечислены в типе
K. - Результат — новый тип, содержащий только выбранные свойства.
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, 'title' | 'description'>;
const obj: TodoPreview = {
title: 'first',
description: 'qwerty'
}
console.log(obj);
Подготовительные Задания (Теория + Практика)
Прежде чем писать MyPick, важно понять три ключевые концепции:
1. Индексированные Типы (Indexed Types)
Это способ получить тип значения по имени ключа.
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Todo['title']; // TodoPreview = string
const title: TodoPreview = 'text';
console.log(title);
Задание 1:
Есть интерфейс:
interface Person {
name: string;
age: number;
}
Как получить тип поля name? Напишите тип NameType.
Решение:
interface Person {
name: string;
age: number;
}
type NameType = Person['name'];
const title: NameType = "Заголовок";
console.log(title);
2. Маппинг по Типу (Mapped Types)
Это способ создать новый тип, пройдясь по списку ключей и определив, что делать с каждым из них.
Пример:
type NewType = {
[P in "a" | "b"]: boolean;
};
// Результат: { a: boolean; b: boolean; }
type Point = { x: number; y: number };
type P = keyof Point; //type P = "x" | "y":
Задание 2:
Из интерфейса Person, cоздайте тип Booleanified, который берёт интерфейс Person и делает все его поля типа boolean.
Решение:
interface Person {
name: string;
age: number;
}
type Booleanified = {
[P in keyof Person]: boolean;
};
// Результат: { name: boolean; age: boolean; }
3. Ключевое слово keyof
Оно возвращает объединение строковых литералов — список всех возможных ключей типа.
Пример:
type Keys = keyof Person; // "name" | "age"
Задание 3:
Что вернёт keyof Car?
interface Car {
brand: string;
year: number;
color: string;
}
type CarKeys = keyof Car; // "brand" | "year" | "color"
Теперь к самому MyPick
Вспомним, что мы хотим:
type MyPick<T, K> = ...
Где T — исходный тип (например, Todo), а K — перечень ключей, которые мы хотим "взять".
Как работает Pick<T, 'title' | 'completed'>?
- Он берёт
T(в нашем случаеTodo). - Он берёт
K('title' | 'completed'). - Он создаёт новый тип, в котором есть только те поля, чьи имена перечислены в
K.
Как это реализовать?
- Мы знаем, что
K— это объединение ключей, например'title' | 'completed'. - Мы должны "пройтись" по каждому ключу из
Kи взять его тип изT. - Это идеальный случай для маппинга по типу!
{
[P in K]: T[P]
}
P in K— проходимся по каждому ключу изK.T[P]— получаем тип поляPизT.
НО! Нужно убедиться, что K — это валидные ключи из T.
Если мы передадим MyPick<Todo, 'nonexistent'>, TypeScript должен выдать ошибку.
Для этого мы используем K extends keyof T. Это значит: «K должен быть подмножеством ключей T».
Решение:
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>;
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
};