Дженерики: практические примеры
Практические паттерны использования generic-типов в реальных задачах: коллекции, утилиты, API-обёртки, HOF и типобезопасные хранилища — реализованные через параметризованные типы TypeScript.
Зачем нужно
Видеть generics в контексте реальных задач проще, чем изучать синтаксис абстрактно. Эта заметка — коллекция готовых паттернов для копирования и адаптации.
Где используется
- HTTP-клиенты с типизированными ответами
- Коллекции и структуры данных
- HOF:
memoize,retry,debounceс сохранением типов - Формы и валидация
- Event emitter с типизированными событиями
Основной контент
Типизированный fetch
async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> {
const res = await fetch(url, init);
if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`);
return res.json() as Promise<T>;
}
interface User { id: number; name: string; email: string }
interface Post { id: number; title: string; body: string }
const user = await fetchJson<User>("/api/users/1");
const posts = await fetchJson<Post>("/api/posts");
// user: User, posts: Post — типобезопасно
Generic коллекция: Stack
class Stack<T> {
private items: T = ;
push(item: T): this { this.items.push(item); return this; }
pop: T | undefined { return this.items.pop(); }
peek: T | undefined { return this.items.at(-1); }
isEmpty: boolean { return this.items.length === 0; }
get size: number { return this.items.length; }
toArray: T { return [...this.items]; }
}
const stack = new Stack<number>;
stack.push(1).push(2).push(3);
console.log(stack.pop()); // 3
Generic Result тип
type Result<T, E = Error> =
| { ok: true; data: T }
| { ok: false; error: E };
function ok<T>(data: T): Result<T> {
return { ok: true, data };
}
function fail<T, E = Error>(error: E): Result<T, E> {
return { ok: false, error } as Result<T, E>;
}
async function parseUser(raw: unknown): Promise<Result<User>> {
if (typeof raw !== "object" || raw === null) {
return fail(new Error("Invalid data"));
}
// ... дополнительная валидация
return ok(raw as User);
}
Generic memoize
function memoize<Args extends unknown, R>(
fn: (...args: Args) => R
): (...args: Args) => R {
const cache = new Map<string, R>;
return (...args: Args): R => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key)!;
const result = fn(...args);
cache.set(key, result);
return result;
};
}
function expensiveCalc(n: number): number {
return n * n; // имитация дорогого вычисления
}
const memoized = memoize(expensiveCalc);
memoized(5); // вычисляет
memoized(5); // из кэша
Типизированный EventEmitter
type EventMap = Record<string, unknown>;
class TypedEmitter<Events extends EventMap> {
private handlers = new Map<keyof Events, Function>;
on<K extends keyof Events>(
event: K,
handler: (...args: Events[K]) => void
): void {
const list = this.handlers.get(event) ?? ;
list.push(handler);
this.handlers.set(event, list);
}
emit<K extends keyof Events>(event: K, ...args: Events[K]): void {
this.handlers.get(event)?.forEach((fn) => fn(...args));
}
}
// Использование
const emitter = new TypedEmitter<{
message: [text: string, from: string];
error: [err: Error];
}>;
emitter.on("message", (text, from) => {
// text: string, from: string — выведено!
console.log(`${from}: ${text}`);
});
emitter.emit("message", "Hello", "Alice");
emitter.emit("error", new Error("oops"));
Generic guard-функция
function assertDefined<T>(
value: T | null | undefined,
message = "Value is not defined"
): asserts value is T {
if (value == null) throw new Error(message);
}
const user: User | null = getUser;
assertDefined(user, "User not found");
// user: User — TypeScript знает
console.log(user.name);
Частые ошибки
- Дублировать generics без нужды — если функция всегда возвращает
string, generic для возвращаемого значения избыточен. - Не ограничивать
Tтам где нужен доступ к полям —T extends { id: string }даёт доступ кobj.id. - Использовать
anyвместоunknownв HOF —anyотключает все проверки параметров. - Слишком сложные generic — если тип сложно читать, возможно нужен несколько упрощённых функций.
Связанные темы
- Generic функции
- Generic интерфейсы и классы
- Ограничения дженериков -- extends
- Assertion Functions
- _MOC TypeScript