Клас, як і функція, може мати параметри типу - тоді один клас працює з різними типами даних зі збереженням перевірок.
class Repository<T extends { id: number }> {
#items = new Map<number, T>();
save(item: T): void {
this.#items.set(item.id, item);
}
find(id: number): T | undefined {
return this.#items.get(id);
}
all(): T[] {
return [...this.#items.values()];
}
}
const users = new Repository<User>();
users.save({ id: 1, name: 'Оля' });
users.find(1)?.name; // тип User
Обмеження extends гарантує, що в T є id - інакше item.id всередині класу був би помилкою.
Виведення параметра типу з конструктора:
class Box<T> {
constructor(public value: T) {}
}
const box = new Box(42); // Box<number> - тип виведено
Чому статичні члени не можуть використовувати T:
class Repository<T> {
static defaultItem: T; // помилка: static members cannot reference class type parameters
}
Параметр типу належить екземпляру: new Repository<User>() і new Repository<Post>() - різні «версії» з різними T. А статичний член один на весь клас - для всіх екземплярів одразу. Яким має бути T у Repository.defaultItem? Відповіді немає.
Якщо статичному методу потрібен generic, він оголошує власний параметр типу:
class Repository<T> {
static of<U extends { id: number }>(items: U[]): Repository<U> {
const repo = new Repository<U>();
items.forEach((item) => repo.save(item));
return repo;
}
}
Корисні прийоми:
- значення за замовчуванням для параметра:
class Cache<V = string>; - кілька параметрів:
class Store<State, Action extends { type: string }>; thisяк тип у методах - для ланцюжків, що зберігають тип нащадка.
Обмеження: параметри типу зникають під час виконання. Усередині класу не можна написати new T() чи x instanceof T - якщо потрібен конструктор, його передають явно: constructor(private make: new () => T).