Урок 09

Generics: продвинуто

keyof

Возвращает union ключей типа:

type User = { name: string; age: number };

type UserKeys = keyof User; // 'name' | 'age'

Практика:

function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user = { name: 'Алиса', age: 25 };
const name = getValue(user, 'name'); // string
const age = getValue(user, 'age');   // number
getValue(user, 'email'); // ошибка

T[K] — тип значения по ключу.

typeof

Возвращает тип значения:

const user = { name: 'Алиса', age: 25 };
type User = typeof user;
// { name: string; age: number }

Ограничение extends

function longest<T extends { length: number }>(a: T, b: T): T {
    return a.length >= b.length ? a : b;
}

Параметр по умолчанию

type ApiResponse<T = unknown> = {
    data: T;
    status: number;
};

Несколько ограничений

function merge<T extends object, U extends object>(a: T, b: U): T & U {
    return { ...a, ...b };
}

Дженерик с массивом

function groupBy<T, K extends keyof T>(items: T[], key: K): Record<string, T[]> {
    return items.reduce((acc, item) => {
        const group = String(item[key]);
        acc[group] = acc[group] ?? [];
        acc[group].push(item);
        return acc;
    }, {} as Record<string, T[]>);
}

Вложенные дженерики

type Container<T> = {
    items: T[];
};

type Nested = Container<Container<string>>;

Распространение

function map<T, U>(items: T[], fn: (item: T) => U): U[] {
    return items.map(fn);
}

map([1, 2, 3], (n) => n.toString()); // string[]

Generic constraints с keyof

function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
    return items.map((item) => item[key]);
}

const users = [
    { name: 'Алиса', age: 25 },
    { name: 'Боб', age: 30 },
];

pluck(users, 'name'); // string[]
pluck(users, 'age');  // number[]

Conditional types

Синтаксис T extends U ? X : Y:

type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

Infer

Извлекает тип из другого типа:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type A = ReturnType<() => string>; // string

Distributive conditional types

Условные типы распространяются на union:

type NonNullable<T> = T extends null | undefined ? never : T;

type A = NonNullable<string | null>; // string

Итоги

  • keyof — union ключей
  • typeof — тип значения
  • T[K] — тип значения по ключу
  • extends — ограничение
  • Условные типы — T extends U ? X : Y
  • infer — извлечение типа