Дженерик-типы и интерфейсы
Дженерик-тип
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