IlmHamroh
JavaScript Full-stack/15-qism. TypeScript22/47-dars19 daqiqa
Mundarija (37)

TypeScript generic interface, type va klass: ApiResponse<T> va Queue<T>

Qisqacha: Tur parametri faqat funksiyada emas, type, interface va class da ham bo'ladi: type ApiResponse<T> = { ok: true; data: T } | { ok: false; error: string }. Endi ApiResponse<Dish[]>, ApiResponse<Order> — bitta qolipdan yasalgan aniq turlar. Generic klass (class Queue<T>) esa har nusxasi o'z turi bilan ishlaydigan konteyner.

Bu darsda

  • Generic type va interface yozasiz: ApiResponse<T>, Page<T>.
  • Standart tur parametrini (<T, E = string>) ishlatasiz va uning qoidasini bilasiz.
  • Generic klass yozasiz: navbat Queue<T> va Repository<T extends …>.
  • Promise<T> ni async funksiyalar bilan to'g'ri tiplaysiz.
  • "Javob turi — faqat va'da" tuzog'ini taniysiz.

Oldin bilishingiz kerak: Generic cheklovlar va keyof, Discriminated union va to'liqlik tekshiruvi, Klasslar TypeScript'da, async / await.

1. Nega bu kerak?

O'tgan darsda generic funksiyalarga cheklov qo'ydik. Endi «Bahor» ilovasining server bilan ishlashiga qaraymiz. Har so'rov javobi bir xil tuzilishda keladi: muvaffaqiyat bo'lsa — ok: true va ma'lumot, bo'lmasa — ok: false va xato matni. Sardor har javob uchun alohida tur yozdi:

ts
type MenuResponse =
  | { ok: true; data: string[] }
  | { ok: false; error: string };
type OrderResponse =
  | { ok: true; data: { id: number; total: number } }
  | { ok: false; error: string };

const menu: MenuResponse = { ok: true, data: ["Osh", "Manti"] };
console.log(menu.ok && menu.data.length); // 2

Ikki tur — deyarli bir xil. O'nta so'rov bo'lsa — o'nta nusxa. Ertaga xato qismiga code qo'shish kerak bo'lsa, o'n joyni tahrirlash kerak va bittasi albatta unutiladi. Bizga tur qolipi kerak: "javob — muvaffaqiyat (T turidagi ma'lumot bilan) yoki xato".

2. Generic type

2.1 Tur ham parametr oladi

Generics darsida funksiya tur parametri olardi. type ham xuddi shunday:

ts
type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };

type Dish = { name: string; price: number };

const menu: ApiResponse<Dish[]> = {
  ok: true,
  data: [{ name: "Osh", price: 35000 }],
};
const failed: ApiResponse<Dish[]> = {
  ok: false,
  error: "Tarmoq yo'q",
};
console.log(menu.ok, failed.ok); // true false

ApiResponse<T> — generic tur (turlar uchun qolip). ApiResponse<Dish[]> — qolipga Dish[] qo'yilgan aniq tur: { ok: true; data: Dish[] } | { ok: false; error: string }. Xato qismi bir marta yozilgan — o'zgartirish ham bir joyda.

Bu Discriminated union — ok maydoni ajratuvchi. Toraytirish ham avvalgidek ishlaydi:

ts
type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };

function show(res: ApiResponse<string[]>): string {
  if (!res.ok) {
    return `Xato: ${res.error}`;
  }
  return res.data.join(", ");
}

console.log(show({ ok: true, data: ["Osh", "Manti"] })); // Osh, Manti
console.log(show({ ok: false, error: "500" })); // Xato: 500

!res.ok shoxida res.error bor, undan keyin esa res.data — string[]. T toraytirishga xalaqit bermaydi.

2.2 Tur argumentini unutish

Generic tur chaqirilgan funksiya emas — u o'zi tur argumentini chiqara olmaydi. Uni yozish shart:

ts
type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };
const res: ApiResponse = { ok: false, error: "500" };

Kompilyator:

text
TS2314: Generic type 'ApiResponse' requires 1 type argument(s).

Tarjimasi: "ApiResponse generic turi 1 ta tur argumentini talab qiladi". TypeScript aytyapti: "qolip berdingiz, lekin uning bo'sh joyini to'ldirmadingiz — data nima turda?" Massiv va tuple turlari darsidagi Array siz xabarning o'zi. Array<T> ham — TypeScript'ning o'zi yozgan generic tur.

3. Generic interface

3.1 Sahifalangan ro'yxat

interface ham tur parametri oladi. Masalan, serverdan buyurtmalar sahifama-sahifa keladi:

ts
interface Page<T> {
  items: T[];
  page: number;
  total: number;
}

type Order = { id: number; table: number };

const first: Page<Order> = {
  items: [{ id: 101, table: 7 }],
  page: 1,
  total: 23,
};
console.log(first.items[0]?.table, first.total); // 7 23

Page<Order> — buyurtmalar sahifasi, Page<Dish> — taomlar sahifasi. Tuzilish bir xil, element turi har xil.

Noto'g'ri element — aniq xato:

ts
interface Page<T> {
  items: T[];
  total: number;
}
const names: Page<string> = { items: [1, 2], total: 2 };

Kompilyator:

text
TS2322: Type 'number' is not assignable to type 'string'.
TS2322: Type 'number' is not assignable to type 'string'.

Tarjimasi: "number turini string turiga berib bo'lmaydi" — har son uchun bittadan. Page<string> da items — string[], biz esa sonlar berdik.

3.2 Generic turlar bir-birini o'raydi

Generic turlarni ichma-ich qo'yish mumkin: ApiResponse<Page<Order>> — "javob: muvaffaqiyat bo'lsa — buyurtmalar sahifasi". O'qishni ichkaridan boshlang: Order → uning sahifasi → shu sahifaning javobi. Kompilyator ham qoliplarni ichkaridan tashqariga to'ldiradi:

Qadam Nima bo'ldi? Natija
1 Page<T> ga T = Order qo'yildi { items: Order[]; page: number; total: number }
2 ApiResponse<T> ga 1-qadam natijasi qo'yildi { ok: true; data: Page<Order> } | { ok: false; error: string }
3 res.ok tekshirildi — birinchi variant qoldi res.data.items — Order[]

Siz bilgan tayyor turlar ham aynan shunday yozilgan. TypeScript'ning lib fayllarida interface Array<T> { … }, interface Map<K, V> { … }, interface Promise<T> { … } bor. Ular siz yozayotgan generic interfeyslardan hech narsasi bilan farq qilmaydi.

3.3 Generic interfeys va uni amalga oshiradigan klass

Interfeysda metodlar ham T dan foydalanadi. Masalan, ofitsiant buyurtma qoralamasini saqlaydigan "ombor" shartnomasi:

ts
interface Store<T> {
  get(key: string): T | undefined;
  set(key: string, value: T): void;
}

class MemoryStore<T> implements Store<T> {
  #data = new Map<string, T>();
  get(key: string): T | undefined {
    return this.#data.get(key);
  }
  set(key: string, value: T): void {
    this.#data.set(key, value);
  }
}

const drafts: Store<string> = new MemoryStore<string>();
drafts.set("stol-7", "2 osh, 1 choy");
console.log(drafts.get("stol-7")); // 2 osh, 1 choy

class MemoryStore<T> implements Store<T> — klassning T si interfeysning T siga uzatildi (Klasslar TypeScript'da darsidagi implements). Ertaga xuddi shu interfeysni localStorage bilan ishlaydigan BrowserStore<T> ham amalga oshirishi mumkin. Kod esa Store<string> bilan ishlayveradi — qaysi ombor ekanini bilmaydi ham.

type yoki interface — qaysi biri? type va interface darsidagi qoida generic'da ham o'zgarmaydi. Union kerak bo'lsa (ApiResponse) — faqat type. Klass amalga oshiradigan shartnoma yoki kengaytiriladigan obyekt shakli bo'lsa — ko'pincha interface.

Tekshirib ko'ring: ApiResponse<Page<Order>> turidagi res da res.ok tekshirilgandan keyin res.data.items[0] ning turi qanday (noUncheckedIndexedAccess yoqilgan)?

Javob

Order | undefined. res.data — Page<Order>, items — Order[], indeks bo'yicha element esa bayroq tufayli undefined bo'lishi ham mumkin.

4. Standart tur parametri

4.1 E = string

Ko'p javoblarda xato — oddiy satr. Lekin ba'zan xato kodli obyekt kerak. Ikkinchi tur parametrini qo'shamiz va unga standart qiymat beramiz:

ts
type ApiResponse<T, E = string> =
  | { ok: true; data: T }
  | { ok: false; error: E };

type ApiError = { code: number; message: string };

const simple: ApiResponse<number> = { ok: false, error: "Topilmadi" };
const detailed: ApiResponse<number, ApiError> = {
  ok: false,
  error: { code: 404, message: "Topilmadi" },
};
console.log(simple.ok, detailed.ok); // false false

E = string — standart tur parametri (default type parameter): argument berilmasa, E — string. ApiResponse<number> avvalgidek ishlaydi, kerak bo'lganda esa ApiResponse<number, ApiError>. Bu funksiyalardagi standart parametr qiymatining (function f(x = 5)) tur darajasidagi aksi.

4.2 Tartib qoidasi

Standart qiymatli parametrlar oxirida turishi kerak:

ts
type Pair<A = string, B> = [A, B];

Kompilyator:

text
TS2706: Required type parameters may not follow optional type parameters.

Tarjimasi: "Majburiy tur parametrlari ixtiyoriylaridan keyin kela olmaydi". Funksiya parametrlaridagi qoidaning o'zi: ixtiyoriylar — oxirida.

Standart qiymat cheklov bilan birga ham yoziladi: <T extends object = Record<string, unknown>> — "T obyekt bo'lsin, berilmasa — istalgan kalitli lug'at".

Tekshirib ko'ring: type Result<T = void, E = Error> = … — Result ni argumentsiz yozsa bo'ladimi? Result<string> da E nima?

Javob

Bo'ladi: ikkala parametrning ham standart qiymati bor, Result — Result<void, Error>. Result<string> da T = string, E esa standart — Error. Argumentlar chapdan o'ngga to'ldiriladi, qolganlari standartdan olinadi.

5. Generic klass

5.1 Oshxona navbati

Queue va deque darsida navbatni yozgansiz: birinchi kelgan buyurtma birinchi tayyorlanadi. Endi uni istalgan element turi bilan ishlaydigan qilamiz:

ts
class Queue<T> {
  #items: T[] = [];

  enqueue(item: T): void {
    this.#items.push(item);
  }

  dequeue(): T | undefined {
    return this.#items.shift();
  }

  get size(): number {
    return this.#items.length;
  }
}

const kitchen = new Queue<string>();
kitchen.enqueue("Osh");
kitchen.enqueue("Manti");
console.log(kitchen.dequeue(), kitchen.size); // Osh 1

class Queue<T> — generic klass. T klassning hamma joyida ishlatiladi: maydonda (T[]), parametrda (item: T), natijada (T | undefined). new Queue<string>() — satrlar navbati: endi kitchen.enqueue(35000) — TS2345.

shift() — massiv boshidan olib tashlaydi va O(n) vaqt oladi. O'sha darsdagi bosh ko'rsatkichli navbat tezroq edi. Bu yerda tezlik emas, turlar muhim — algoritmni almashtirsangiz ham, klass tashqi ko'rinishi (Queue<T>) o'zgarmaydi.

5.2 Tur argumenti konstruktordan chiqariladi

Konstruktor T turidagi qiymat olsa, new paytida tur argumentini yozish shart emas:

ts
class Box<T> {
  readonly value: T;
  constructor(value: T) {
    this.value = value;
  }
}

const price = new Box(35000);
const dish = new Box("Osh");
console.log(price.value + 5000, dish.value.length); // 40000 3

Hover'da price: Box<number>, dish: Box<string>. Lekin Queue ning konstruktori hech narsa olmaydi — undan T ni chiqarib bo'lmaydi. new Queue() yozilsa, tur Queue<unknown> bo'ladi (Generics darsidagi "chiqarilmasa — unknown" qoidasi). Shuning uchun bo'sh konteynerda tur argumentini yozing: new Queue<string>().

5.3 static a'zolar T ni ko'rmaydi

ts
class Queue<T> {
  static empty: T[] = [];
}

Kompilyator:

text
TS2302: Static members cannot reference class type parameters.

Tarjimasi: "Statik a'zolar klassning tur parametrlariga murojaat qila olmaydi". Mantiqi: T har nusxa uchun alohida (Queue<string>, Queue<number>), static a'zo esa bitta — klassning o'ziga tegishli (static a'zolar). U qaysi T ni olishini bilmaydi.

5.4 Cheklovli generic klass: Repository<T>

Ma'lumotlarni id bo'yicha saqlaydigan klass — real loyihalarda ko'p uchraydigan repository naqshi. Unga cheklov kerak: element id ga ega bo'lsin:

ts
class Repository<T extends { readonly id: number }> {
  #items = new Map<number, T>();

  add(item: T): void {
    this.#items.set(item.id, item);
  }

  findById(id: number): T | undefined {
    return this.#items.get(id);
  }
}

type Guest = { readonly id: number; name: string; people: number };
const guests = new Repository<Guest>();
guests.add({ id: 1, name: "Dilshod aka", people: 4 });
console.log(guests.findById(1)?.name); // Dilshod aka

Bu Generic cheklovlar darsidagi T extends … ning klassdagi shakli. Klass ichida item.id ishlatildi, tashqarida esa findById aniq Guest qaytardi — ?.name ishladi. new Repository<{ name: string }>() esa xato beradi: TS2741: Property 'id' is missing in type '{ name: string; }' but required in type '{ readonly id: number; }'. — tur argumenti cheklovni bajarmadi.

Generic klassning tuzilishini chizmada ko'ramiz:

classDiagram
  class Repository~T~ {
    -items Map~number, T~
    +add(item T) void
    +findById(id number) T
  }
  class Guest {
    +id number
    +name string
  }
  class Order {
    +id number
    +total number
  }
  Repository~T~ ..> Guest : T = Guest
  Repository~T~ ..> Order : T = Order

Bitta klass — ikki xil nusxa: Repository<Guest> va Repository<Order>. Har biri faqat o'z turidagi yozuvlarni qabul qiladi.

Tekshirib ko'ring: const orders = new Repository<{ id: number; total: number }>(); orders.add({ id: 7 }); — nima bo'ladi?

Javob

TS2741: Property 'total' is missing…: T — { id: number; total: number }, add esa aynan T ni kutadi. { id: 7 } da total yo'q. Klass cheklovi faqat kamida id ni talab qiladi, nusxaning T si esa to'liq turni.

6. Promise<T> va async

6.1 async funksiya doim Promise qaytaradi

async / await darsidan bilasiz: async funksiya natijasi — va'da (Promise). TypeScript'da bu Promise<T>: "oxir-oqibat T beradi".

ts
type Dish = { name: string; price: number };

async function loadMenu(): Promise<Dish[]> {
  return [{ name: "Osh", price: 35000 }];
}

const menu = await loadMenu();
console.log(menu[0]?.name); // Osh

return da oddiy massiv yozdik — async uni o'zi Promise'ga o'raydi. await esa o'rovni ochadi: menu — Dish[]. (Bu blok modul, shuning uchun yuqori darajadagi await ishlaydi.)

Natija turini noto'g'ri yozish — keng tarqalgan xato:

ts
async function countOrders(): number {
  return 12;
}

Kompilyator:

text
TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?

Tarjimasi: "async funksiya yoki metodning natija turi global Promise<T> turi bo'lishi kerak. Promise<number> deb yozmoqchimidingiz?" TypeScript tuzatishni ham taklif qildi.

6.2 Promise<ApiResponse<T>>

Generic turlar Promise ichida ham ishlaydi — server bilan ishlaydigan funksiyaning odatiy imzosi:

ts
type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };

async function fetchTables(): Promise<ApiResponse<number[]>> {
  return { ok: true, data: [1, 2, 7] };
}

const res = await fetchTables();
if (res.ok) {
  const list = res.data.join(", ");
  console.log(`Bo'sh stollar: ${list}`); // Bo'sh stollar: 1, 2, 7
}

Ichkaridan o'qing: number[] → uning javobi → shu javobning va'dasi. await dan keyin res — ApiResponse<number[]>, res.ok dan keyin res.data — number[].

6.3 Promise.all turlarni o'rinma-o'rin saqlaydi

Ikki so'rovni parallel yuborish — Promise kombinatorlari darsidagi Promise.all. TypeScript har o'rinning turini alohida kuzatadi:

ts
async function loadMenu(): Promise<string[]> {
  return ["Osh", "Manti"];
}
async function countOrders(): Promise<number> {
  return 12;
}

const [menu, count] = await Promise.all([loadMenu(), countOrders()]);
console.log(menu.length + count); // 14

Hover'da menu: string[], count: number — (string[] | number)[] aralashmasi emas. Promise.all ning turi generic: kirgan va'dalar ro'yxatidan tuple yasaydi va har o'ringa await qilingan turni qo'yadi. Generic'lar standart kutubxonani shunchalik aniq qiladi.

Tekshirib ko'ring: async function f() { return [1, 2]; } — natija turini yozmagan bo'lsangiz, f ning natija turi qanday chiqariladi?

Javob

Promise<number[]>. return dagi qiymat number[], async esa uni Promise ga o'raydi. Eksport qilinadigan funksiyalarda baribir yozib qo'yish tavsiya etiladi (Tur chiqarish darsidagi qoida) — imzo hujjat vazifasini o'taydi.

7. TypeScript ushlamaydigan tuzoq: javob turi — faqat va'da

ApiResponse<Dish[]> — bizning kutishimiz. Server esa uni bilmaydi. JSON.parse va response.json() any qaytaradi, any esa istalgan turga jim beriladi:

ts
type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };

const raw = JSON.parse('{"ok": true, "data": null}');
const res: ApiResponse<string[]> = raw;
if (res.ok) {
  console.log(res.data.length);
}

tsc — xatosiz. Node esa:

text
TypeError: Cannot read properties of null (reading 'length')

Server data: null yubordi. Tur "string[]" dedi, haqiqatda — null. Generic tur ma'lumotni tasvirlaydi, lekin uni tekshirmaydi. Server javobini tur bilan "yopishtirish" — as ning yashirin shakli.

Haqiqiy himoya — javobni unknown deb olib, ishlash paytida tekshirish. Bu darslarda o'rganganlaringiz bilan qisqa shakli:

ts
type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };

function isStringArray(value: unknown): value is string[] {
  return (
    Array.isArray(value) && value.every((x) => typeof x === "string")
  );
}

function toMenuResponse(raw: unknown): ApiResponse<string[]> {
  if (typeof raw !== "object" || raw === null || !("data" in raw)) {
    return { ok: false, error: "Javob shakli noto'g'ri" };
  }
  if (!isStringArray(raw.data)) {
    return { ok: false, error: "data — satrlar ro'yxati emas" };
  }
  return { ok: true, data: raw.data };
}

const res = toMenuResponse(JSON.parse('{"ok": true, "data": null}'));
console.log(res.ok ? res.data.length : res.error);

Konsolda:

text
data — satrlar ro'yxati emas
  • raw: unknown — any emas: tekshiruvsiz hech narsa qilib bo'lmaydi (any, unknown, never).
  • "data" in raw va isStringArray — toraytirish va tur predikati. Faqat ulardan keyin raw.data string[] deb tan olinadi.
  • Buzuq javob endi TypeError emas, oddiy ok: false — UI xato matnini ko'rsatadi.

Har javob uchun bunday funksiyani qo'lda yozish uzoq. Uni avtomatlashtirish — API ma'lumotlarini tiplash va Zod darslarining mavzusi.

8. Ko'p uchraydigan xatolar

8.1 Generic turga argument bermaslik

const r: ApiResponse = … — TS2314. Tuzatish: ApiResponse<Dish[]> yoki standart parametr qo'shing (<T = unknown>).

8.2 Standart parametrdan keyin majburiy

<A = string, B> — TS2706. Tuzatish: standart qiymatlilarni oxiriga.

8.3 Bo'sh generic klass tur argumentisiz

new Queue() — Queue<unknown>. Tuzatish: new Queue<Order>().

8.4 static maydonda T

TS2302. Tuzatish: statik a'zo umumiy bo'lsin (static readonly MAX = 50) yoki generic statik metod yozing (static from<U>(items: U[]): Queue<U>).

8.5 async funksiya natijasini Promise siz yozish

async function f(): number — TS1064. Tuzatish: Promise<number>.

8.6 Tur argumentiga any berish

ApiResponse<any>, new Queue<any>() — generic qolip bor, lekin ichi bo'sh: res.data yana any, xatolar jim o'tadi. Bu ko'pincha "tez ishlasin" deb yoziladi va keyin unutiladi. Tuzatish: aniq tur yozing. Hali noma'lum bo'lsa — unknown: u hech bo'lmasa ishlatishdan oldin tekshirtiradi. ESLint'ning no-explicit-any qoidasi (vazifalar da yoqilgan) bunday joylarni ko'rsatadi.

8.7 Bir xil tuzilishli turlarni nusxalash

MenuResponse, OrderResponse, GuestResponse — farqi faqat data turida. Tuzatish: bitta ApiResponse<T>. Turlarda ham kodga o'xshab "takrorlama" (DRY) qoidasi ishlaydi: tuzilish o'zgarsa, bir joyda o'zgaradi.

9. Mashqlar

1-mashq (oson): Turni yig'ing

ts
type ApiResponse<T, E = string> =
  | { ok: true; data: T }
  | { ok: false; error: E };
interface Page<T> {
  items: T[];
  total: number;
}
const res: ApiResponse<Page<string>> = {
  ok: true,
  data: { items: ["Osh"], total: 1 },
};
console.log(res.ok && res.data.items.length); // 1

res.data.items ning turi (ok tekshirilgandan keyin): [:string[]]. ApiResponse<number> dagi error ning turi: [:string]. ApiResponse da nechta tur parametri bor: .

Yechim
  • string[]: Page<string> dagi items: T[], T = string.
  • string: E berilmadi — standart qiymat ishladi.
  • 2 ta: T (majburiy) va E (standart qiymatli).

2-mashq (o'rta): Generic Stack<T>

Oxirgi kelgan — birinchi chiqadigan stek klassini yozing (Stek darsidagi tuzilma): push(item), pop() (bo'sh bo'lsa undefined), peek(), size getter. Ichki massiv tashqaridan ko'rinmasin.

Yechim
ts
class Stack<T> {
  #items: T[] = [];

  push(item: T): void {
    this.#items.push(item);
  }

  pop(): T | undefined {
    return this.#items.pop();
  }

  peek(): T | undefined {
    return this.#items.at(-1);
  }

  get size(): number {
    return this.#items.length;
  }
}

const plates = new Stack<number>();
plates.push(1);
plates.push(2);
console.log(plates.pop(), plates.peek(), plates.size); // 2 1 1
  • #items — haqiqatan yopiq (Klasslar TypeScript'da): tashqi kod stek tartibini buzolmaydi.
  • pop va push — massivning oxirida, ikkalasi ham O(1).
  • new Stack<number>() — konstruktor qiymat olmaydi, shuning uchun tur argumenti yozildi.

3-mashq (qiyin): Amaliy tajriba — navbat va repository

Mashq kurs/mashqlar/15/ papkasida. Ikki fayl:

  • 22-generic-klass/queue.ts — Queue<T>: enqueue, dequeue, peek, size.
  • 22-generic-klass/repository.ts — ApiResponse<T, E = string> turi va Repository<T extends { readonly id: number }>: add(item) (band id — RangeError), findById(id) — ApiResponse<T> qaytarsin (topilmasa ok: false), all().

Avval Repository ni generic'siz yozib ko'ring: ichkarida type Entity = { readonly id: number }, add(item: Entity), findById(id): Entity | undefined. Test faylda mehmon qo'shing ({ id: 1, name: "Malika" }) va uning ismini o'qing. npx tsc --pretty false nima deydi?

Yechim

Generic'siz variantda tsc 7.0.2 test faylda ikki xato topdi:

text
22-generic-klass/repository.test.ts(8,23): error TS2353: Object literal may only specify known properties, and 'name' does not exist in type 'Entity'.
22-generic-klass/repository.test.ts(9,36): error TS2339: Property 'name' does not exist on type 'Entity'.

Birinchisi — add ga name berib bo'lmaydi: Entity da faqat id (ortiqcha xususiyat tekshiruvi). Ikkinchisi — topilgan yozuvda name yo'q. Klass faqat id ni biladi, qolgan hamma narsani "unutadi". Generic klass esa har nusxada to'liq turni saqlaydi.

ts
// 22-generic-klass/queue.ts — oshxona navbati (FIFO)
export class Queue<T> {
  #items: T[] = [];

  enqueue(item: T): void {
    this.#items.push(item);
  }

  dequeue(): T | undefined {
    return this.#items.shift();
  }

  peek(): T | undefined {
    return this.#items[0];
  }

  get size(): number {
    return this.#items.length;
  }
}
ts
// 22-generic-klass/repository.ts — id bo'yicha saqlanadigan yozuvlar
export type ApiResponse<T, E = string> =
  | { readonly ok: true; readonly data: T }
  | { readonly ok: false; readonly error: E };

export class Repository<T extends { readonly id: number }> {
  #items = new Map<number, T>();

  add(item: T): void {
    if (this.#items.has(item.id)) {
      throw new RangeError(`id band: ${item.id}`);
    }
    this.#items.set(item.id, item);
  }

  findById(id: number): ApiResponse<T> {
    const item = this.#items.get(id);
    if (item === undefined) {
      return { ok: false, error: `Topilmadi: ${id}` };
    }
    return { ok: true, data: item };
  }

  all(): T[] {
    return [...this.#items.values()];
  }
}
ts
// 22-generic-klass/repository.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { Queue } from "./queue.ts";
import { Repository } from "./repository.ts";

type Guest = { readonly id: number; name: string };

test("Queue: birinchi kelgan — birinchi chiqadi", () => {
  const kitchen = new Queue<string>();
  kitchen.enqueue("Osh");
  kitchen.enqueue("Manti");
  assert.equal(kitchen.dequeue(), "Osh");
  assert.equal(kitchen.peek(), "Manti");
  assert.equal(kitchen.size, 1);
});

test("Repository: qo'shish va topish", () => {
  const guests = new Repository<Guest>();
  guests.add({ id: 1, name: "Malika" });
  const found = guests.findById(1);
  assert.equal(found.ok && found.data.name, "Malika");
  const missing = guests.findById(2);
  assert.equal(missing.ok ? "" : missing.error, "Topilmadi: 2");
});

test("Repository: band id — RangeError", () => {
  const guests = new Repository<Guest>();
  guests.add({ id: 1, name: "Malika" });
  const twin = { id: 1, name: "Bobur" };
  assert.throws(() => guests.add(twin), RangeError);
  assert.equal(guests.all().length, 1);
});
  • findById endi undefined emas, ApiResponse<T> qaytaradi: chaqiruvchi ok ni tekshirmasdan data ni ololmaydi, xato sababi ham keladi.
  • found.ok && found.data.name — && ning o'ng tomonida found allaqachon toraygan — data bor.
  • [...this.#items.values()] — Map qiymatlaridan yangi massiv: tashqi kod ichki Map ni buzolmaydi.

npx tsc --pretty false — chiqishsiz. node --test 22-generic-klass/repository.test.ts bizda (Node 24.21.0) shunday chiqdi:

text
✔ Queue: birinchi kelgan — birinchi chiqadi (2.4746ms)
✔ Repository: qo'shish va topish (0.6098ms)
✔ Repository: band id — RangeError (1.3574ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 276.8654

XATOLAR.md ga qo'shing: TS2314, TS2706, TS2302, TS1064. Keyin kurs/mashqlar papkasidan:

bash
git add 15/22-generic-klass 15/XATOLAR.md
git commit -m "15/22: Queue<T> va Repository<T> — generic klasslar"

10. Real ishda

  • API qatlami: deyarli har frontend loyihada ApiResponse<T>, Paginated<T> yoki shunga o'xshash turlar bor. vazifalar ning turlar.ts faylida ham shunday tur bor — Natija<T, E> (muvaffaqiyat yoki xato); u Async kod va xatolarni tiplash qadamida qo'shiladi.
  • Kutubxonalar: React'dagi useState<T>, useRef<T>, server kutubxonalarida Repository<User> — hammasi generic interfeys yoki klass.
  • Ma'lumotlar tuzilmalari: Queue<T>, Stack<T>, LRUCache<K, V> — 14-qismdagi tuzilmalarning TypeScript'dagi tabiiy ko'rinishi.
  • Intervyu: "Generic klass yozing (stek yoki navbat)", "Standart tur parametri nima?", "Nega static a'zo T ni ko'rmaydi?" — tez-tez beriladi.

Xulosa

  • type, interface va class ham tur parametri oladi: ApiResponse<T>, Page<T>, Queue<T>. Generic tur — turlar uchun qolip.
  • Generic turga argument berish shart (TS2314), yoki standart qiymat: <T, E = string>. Standartlilar — oxirida (TS2706).
  • Generic klassda T har nusxa uchun alohida; konstruktordan chiqariladi, bo'sh konteynerda esa yoziladi. static a'zolar T ni ko'rmaydi.
  • async funksiya — Promise<T>. Promise<ApiResponse<T>> — server funksiyasining odatiy imzosi.
  • Generic tur ma'lumotni tasvirlaydi, tekshirmaydi: server javobi baribir ishlash paytida tekshiriladi.

Keyingi dars: Generics ilg'or amaliyoti — const tur parametrlari, NoInfer va generic'ni qachon yozmaslik kerak.

Manbalar

  • TypeScript Handbook: "Generics" (Generic Types, Generic Classes, Generic Parameter Defaults) — typescriptlang.org/docs/handbook/2/generics.html
  • TypeScript Handbook: "Classes" (Generic Classes) — typescriptlang.org/docs/handbook/2/classes.html#generic-classes
  • TypeScript Handbook: "Everyday Types" (Promise) va "More on Functions" — typescriptlang.org/docs/handbook/2/everyday-types.html
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
TypeScript generic interface, type va klass: ApiResponse<T> va Queue<T> — IlmHamroh