Typescript - challenge MyPick

typescript

Нужно создать свой собственный тип 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,
};