Урок 05

Дженерик-типы и интерфейсы

Дженерик-тип

type Box<T> = {
    value: T;
};

const stringBox: Box<string> = { value: 'hello' };
const numberBox: Box<number> = { value: 42 };

Дженерик-интерфейс

interface Box<T> {
    value: T;
}

Работают одинаково. type гибче, interface умеет declaration merging.

Несколько параметров

type Pair<K, V> = {
    key: K;
    value: V;
};

const entry: Pair<string, number> = { key: 'age', value: 25 };

Значение по умолчанию

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

const r1: ApiResponse = { data: 'any', status: 200 };
const r2: ApiResponse<User> = { data: user, status: 200 };

Ограничения

type Repository<T extends { id: number }> = {
    find(id: number): T | undefined;
    save(item: T): void;
};

T обязан иметь id: number.

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

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

type Nested = Container<Container<string>>;
// { items: { items: string[] }[] }

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

type List<T> = T[];
type Dict<V> = Record<string, V>;

type Users = List<User>;
type UserById = Dict<User>;

Дженерик-функция в типе

type Mapper<Input, Output> = (input: Input) => Output;

const toString: Mapper<number, string> = (n) => n.toString();

Реальные примеры

Result

type Result<T, E = Error> =
    | { ok: true; value: T }
    | { ok: false; error: E };

function parseNumber(s: string): Result<number> {
    const n = Number(s);
    return Number.isNaN(n)
        ? { ok: false, error: new Error('NaN') }
        : { ok: true, value: n };
}

Paginated

type Paginated<T> = {
    data: T[];
    total: number;
    page: number;
    perPage: number;
};

type PaginatedUsers = Paginated<User>;

AsyncState

type AsyncState<T> =
    | { status: 'idle' }
    | { status: 'loading' }
    | { status: 'success'; data: T }
    | { status: 'error'; error: Error };

type UserState = AsyncState<User>;

Tree

type TreeNode<T> = {
    value: T;
    children: TreeNode<T>[];
};

Рекурсивный дженерик. Полезен для категорий, меню.

Partial generic

type WithLoading<T> = T & { loading: boolean };

const user: WithLoading<User> = {
    name: 'Алиса',
    loading: false,
};

Комбинация с утилитами

type UpdatePayload<T> = Partial<Omit<T, 'id' | 'createdAt'>>;

type UserUpdate = UpdatePayload<User>;

Итоги

  • Дженерик-типы и интерфейсы работают одинаково
  • Дефолтные значения для параметров
  • Ограничения через extends
  • Вложенные и рекурсивные
  • Реальные примеры: Result, Paginated, AsyncState