Дженерик-функции и классы
Generic функции и классы
Generic функции
// Базовый синтаксис
function wrap<T>(value: T): { value: T } {
return { value }
}
// Стрелочные функции
const unwrap = <T>(wrapper: { value: T }): T => wrapper.value
// Несколько параметров
function pick<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: 'Иван', age: 30 }
const name = pick(user, 'name') // тип: string
const age = pick(user, 'age') // тип: number
Ограничения (Constraints)
Ограничения позволяют требовать, чтобы параметр типа имел определённые свойства:
// T должен иметь свойство length
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b
}
longest('hello', 'world') // OK — строки имеют length
longest([1, 2, 3], [4, 5]) // OK — массивы имеют length
// longest(1, 2) // Ошибка — числа не имеют length
// keyof — ограничение ключами другого типа
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
Ограничение
extends в дженериках — это не наследование классов! Это означает «должен быть совместим с / должен иметь такую форму».Generic классы
// Простой generic контейнер
class Box<T> {
private value: T
constructor(value: T) {
this.value = value
}
getValue(): T {
return this.value
}
map<U>(fn: (value: T) => U): Box<U> {
return new Box(fn(this.value))
}
}
const numBox = new Box(42)
const strBox = numBox.map(n => n.toString()) // Box<string>
// Generic стек
class Stack<T> {
private items: T[] = []
push(item: T): void {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
peek(): T | undefined {
return this.items[this.items.length - 1]
}
get size(): number {
return this.items.length
}
isEmpty(): boolean {
return this.items.length === 0
}
}
const stack = new Stack<number>()
stack.push(1)
stack.push(2)
console.log(stack.pop()) // 2
Generic интерфейсы
// Generic интерфейс для репозитория
interface Repository<T, ID = number> {
findById(id: ID): Promise<T | null>
findAll(): Promise<T[]>
save(entity: T): Promise<T>
delete(id: ID): Promise<void>
}
interface User {
id: number
name: string
email: string
}
// Реализация
class UserRepository implements Repository<User> {
private users: User[] = []
async findById(id: number): Promise<User | null> {
return this.users.find(u => u.id === id) ?? null
}
async findAll(): Promise<User[]> {
return [...this.users]
}
async save(user: User): Promise<User> {
const index = this.users.findIndex(u => u.id === user.id)
if (index >= 0) {
this.users[index] = user
} else {
this.users.push(user)
}
return user
}
async delete(id: number): Promise<void> {
this.users = this.users.filter(u => u.id !== id)
}
}
TypeScript использует структурную типизацию, поэтому дженерики в TypeScript бивариантны для методов и ковариантны для readonly свойств.
// Ковариантность: если Dog extends Animal,
// то ReadonlyArray<Dog> совместим с ReadonlyArray<Animal>
const dogs: ReadonlyArray<Dog> = []
const animals: ReadonlyArray<Animal> = dogs // OK
// Но Array<Dog> НЕ совместим с Array<Animal>
// (из-за возможности записи)
const mutableDogs: Array<Dog> = []
// const mutableAnimals: Array<Animal> = mutableDogs // Ошибка
Это важно для правильного моделирования типов коллекций.
Значения по умолчанию для параметров типов
// Параметр типа с дефолтным значением
interface ApiResponse<T = unknown> {
data: T
status: number
message: string
}
// Использование без явного параметра
const response: ApiResponse = {
data: 'что угодно',
status: 200,
message: 'OK'
}
// С явным параметром
const userResponse: ApiResponse<User> = {
data: { id: 1, name: 'Иван', email: 'ivan@example.com' },
status: 200,
message: 'OK'
}