IlmHamroh
JavaScript Full-stack/15-qism. TypeScript21/47-dars19 daqiqa
Mundarija (30)

TypeScript generic cheklovlar va keyof: T extends … va K extends keyof T

Qisqacha: Cheklov (constraint) generic'ga "kamida shu shaklda bo'lsin" talabini qo'yadi: function longest<T extends { length: number }>(a: T, b: T): T. Endi ichkarida a.length ishlatish mumkin, natija esa baribir aniq T. keyof T — T ning kalitlari union'i, K extends keyof T esa "faqat mavjud kalit" degani: getProperty(dish, "price") natijasi — number.

Bu darsda

  • T extends … bilan generic'ga cheklov qo'yasiz va nega u oddiy parametr turidan yaxshiroq ekanini ko'rsatasiz.
  • keyof operatori bilan obyekt kalitlarining union'ini olasiz.
  • getProperty<T, K extends keyof T> va T[K] natija turini yozasiz.
  • Bir nechta tur parametrini bir-biriga bog'laysiz.
  • vazifalar dagi qidiruv, saralash, taymer modullarini generic cheklovlar bilan .ts ga ko'chirasiz.

Oldin bilishingiz kerak: Generics: turni parametr qilish, Index signature va Record, Kengaytirish, kesishma va declaration merging, Strukturaviy tiplash.

1. Nega bu kerak?

O'tgan darsda cheklovsiz T "qora quti" ekanini ko'rdik: value.length — TS2339. Lekin ko'p funksiyalarga "istalgan tur" emas, "uzunligi bor istalgan tur" yoki "narxi bor istalgan obyekt" kerak.

Birinchi urinish — generic'siz, oddiy parametr turi bilan. Ikki matndan uzunrog'ini oladigan funksiya:

ts
function longest(a: { length: number }, b: { length: number }) {
  return a.length >= b.length ? a : b;
}
const title = longest("Osh", "Manti");
console.log(title.toUpperCase());

Kompilyator:

text
TS2339: Property 'toUpperCase' does not exist on type '{ length: number; }'.

Tarjimasi: "{ length: number; } turida toUpperCase xususiyati yo'q". Funksiya ishlaydi, lekin natija turi — { length: number }: "uzunligi bor nimadir". Biz satr berdik, lekin satr ekanini funksiya "unutdi". Bu Generics darsidagi unknown muammosining yarim versiyasi. Bizga ikkalasi kerak: ichkarida length ishlatish va natijada aniq tur.

2. T extends …: generic'ga cheklov

2.1 Yozuv

ts
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

const title = longest("Osh", "Manti");
const list = longest([35000], [28000, 30000]);
console.log(title.toUpperCase(), list.length); // MANTI 2

T extends { length: number } — generic cheklov (generic constraint): "T istalgan tur bo'lishi mumkin, lekin unda length: number bo'lishi shart". Ikki natija:

  • Ichkarida a.length — ruxsat: TypeScript T da kamida length borligini biladi.
  • Tashqarida title — string, list — number[]. Aniq tur saqlandi.

Bu yerdagi extends — Kengaytirish darsidagi interface extends bilan bir xil ma'noda: "kamida shu maydonlar bor". Faqat endi u turga emas, tur parametriga qo'yilgan talab.

2.2 Cheklovga mos kelmasa

Talabni bajarmaydigan tur berilsa, chaqiruv xato beradi:

ts
function lengthOf<T extends { length: number }>(value: T): number {
  return value.length;
}
lengthOf(35000);

Kompilyator:

text
TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

Tarjimasi: "number turidagi argumentni { length: number; } turidagi parametrga berib bo'lmaydi". Sonda length yo'q. Xabar cheklovning o'zini ko'rsatadi — nima talab qilinganini darhol ko'rasiz.

Qaysi turlar o'tadi, qaysilari yo'q — rasmda:

flowchart LR
  C{"T extends<br/>{ length: number }"}
  S["string"] --> C
  A["number[]"] --> C
  O["{ length: 3, nomi: 'x' }"] --> C
  N["number"] -.-> C
  C --> OK["o'tadi: length bor"]
  N -.-> X["TS2345: length yo'q"]

Satr, massiv, length maydoni bor har qanday obyekt — o'tadi (Strukturaviy tiplash: shakl mos kelsa yetarli). Son — o'tmaydi.

2.3 Cheklov — interfeys yoki tur nomi

Cheklov sifatida tayyor tur nomini ham berish mumkin. «Bahor» menyusidan eng arzonini topamiz — taomlar ham, ichimliklar ham kelishi mumkin:

ts
interface MenuItem {
  name: string;
  price: number;
}

function cheapest<T extends MenuItem>(
  items: readonly T[],
): T | undefined {
  let best: T | undefined;
  for (const item of items) {
    if (best === undefined || item.price < best.price) best = item;
  }
  return best;
}

const dishes = [
  { name: "Osh", price: 35000, cookMinutes: 40 },
  { name: "Lag'mon", price: 28000, cookMinutes: 25 },
];
const found = cheapest(dishes);
console.log(found?.name, found?.cookMinutes); // Lag'mon 25

Asosiy nuqta — oxirgi qatorda. found?.cookMinutes ishladi, chunki natija turi MenuItem emas, aniq T — { name: string; price: number; cookMinutes: number }. Agar cheapest(items: readonly MenuItem[]): MenuItem deb yozganimizda, cookMinutes yo'qolib, TS2339 chiqardi. Cheklov ichkariga price ni beradi, generic esa tashqariga aniq turni qaytaradi.

vazifalar da aynan shu naqsh bor: tartibla<T extends VazifaMalumoti>(vazifalar: readonly T[], …): T[] — Vazifa klassi obyektlari kirsa, Vazifa[] chiqadi. Bu darsning «Vazifalar qadami» mashqida ko'ramiz.

Tekshirib ko'ring: cheapest([{ name: "Choy" }]) — nima bo'ladi? cheapest([{ name: "Choy", price: 5000, hot: true }]) chi?

Javob

Birinchisi — TS2345: elementda price yo'q, cheklov MenuItem ni talab qiladi. Ikkinchisi — xatosiz: ortiqcha hot maydoni cheklovga xalaqit bermaydi, natija turi esa { name: string; price: number; hot: boolean } | undefined — hot saqlanadi.

3. keyof: obyekt kalitlari union'i

3.1 Operator

keyof — tur operatori: obyekt turining kalitlaridan literal union yasaydi.

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

const key: DishKey = "price";
console.log(key); // price

keyof Dish — "name" | "price" | "cookMinutes". Hover'da ba'zan keyof Dish ko'rinishida qoladi — ma'nosi bir xil. Ro'yxatda bo'lmagan kalit — xato:

ts
type Dish = { name: string; price: number; cookMinutes: number };
const key: keyof Dish = "narx";

Kompilyator:

text
TS2322: Type '"narx"' is not assignable to type 'keyof Dish'.

Tarjimasi: ""narx" turini keyof Dish turiga berib bo'lmaydi". Ya'ni: "Dish da bunday kalit yo'q". keyof ning qiymati shundaki, kalitlar ro'yxati qo'lda takrorlanmaydi. Dish ga yangi maydon qo'shilsa, keyof Dish ham o'zi kengayadi. enum va uning muqobillari darsidagi (typeof X)[keyof typeof X] qolipining o'rtadagi qismi — shu operator.

3.2 getProperty: kalit va qiymat turi bog'langan

Endi keyof ni generic bilan birlashtiramiz. Obyektdan kalit bo'yicha qiymat oladigan funksiya:

ts
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const osh = { name: "Osh", price: 35000, cookMinutes: 40 };
const price = getProperty(osh, "price");
const name = getProperty(osh, "name");
console.log(price + 5000, name.toUpperCase()); // 40000 OSH

Uch qismga ajratamiz:

  • <T, K extends keyof T> — ikkita tur parametri. T — obyekt turi. K — cheklangan: "T ning kalitlaridan biri".
  • key: K — faqat mavjud kalit beriladi.
  • : T[K] — indexed access (indeks bo'yicha tur): "T dagi K kalitining turi". K = "price" bo'lsa — number, K = "name" bo'lsa — string.

Hover'da price: number, name: string — har chaqiruvda kalitga mos aniq tur. getProperty(osh, "price") chaqiruvini kompilyator shunday yechadi:

Qadam Nima bo'ldi? Natija
1 obj ga osh keldi T = { name: string; price: number; cookMinutes: number }
2 keyof T hisoblandi "name" | "price" | "cookMinutes"
3 key ga "price" keldi, ro'yxatda bor K = "price"
4 T[K] — T dagi price maydonining turi number

"narx" berilsa, 3-qadamda to'xtaydi: u 2-qadamdagi ro'yxatda yo'q. T[K] yozuvini keyof, typeof va indexed access darsida to'liq o'rganamiz.

Noto'g'ri kalit — chaqiruvning o'zida xato:

ts
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
type Dish = { name: string; price: number };
const osh: Dish = { name: "Osh", price: 35000 };
getProperty(osh, "narx");

Kompilyator:

text
TS2345: Argument of type '"narx"' is not assignable to parameter of type 'keyof Dish'.

Tarjimasi: ""narx" turidagi argumentni keyof Dish turidagi parametrga berib bo'lmaydi". Muharrirda bu yanada qulay: getProperty(osh, " yozganingizda avtomatik to'ldirish faqat name va price ni taklif qiladi.

3.3 Cheklovsiz kalit — TS7053

K extends keyof T o'rniga oddiy string yozsak-chi?

ts
function getAny<T>(obj: T, key: string) {
  return obj[key];
}

Kompilyator:

text
TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'.
  No index signature with a parameter of type 'string' was found on type 'unknown'.

Tarjimasi: "string turidagi ifoda bilan unknown ga indeks berib bo'lmaydi, shuning uchun element yashirincha any bo'ldi". Bu Index signature va Record darsidagi TS7053 ning o'zi: istalgan satr bilan indeks berib bo'lmaydi. Cheklovsiz T ning o'zi ham unknown deb ko'rildi. K extends keyof T ikkala muammoni birdan hal qiladi.

Tekshirib ko'ring: type Guest = { name: string; phone?: string } uchun getProperty(guest, "phone") natijasining turi qanday?

Javob

string | undefined. T[K] maydonning to'liq turini oladi — ixtiyoriy maydonda u undefined ni ham o'z ichiga oladi. Shuning uchun natijani ishlatishdan oldin tekshirish kerak (null va undefined).

4. Bir nechta tur parametri

4.1 Parametrlar bir-biriga tayanadi

getProperty da K ning cheklovi T ga bog'liq edi. Shu usul bilan ro'yxatdan bitta maydonni yig'adigan funksiya:

ts
function pluck<T, K extends keyof T>(
  items: readonly T[],
  key: K,
): T[K][] {
  return items.map((item) => item[key]);
}

const menu = [
  { name: "Osh", price: 35000 },
  { name: "Manti", price: 30000 },
];
console.log(pluck(menu, "name")); // [ 'Osh', 'Manti' ]
console.log(pluck(menu, "price")); // [ 35000, 30000 ]

pluck(menu, "name") — string[], pluck(menu, "price") — number[]. Natija turi kalitga qarab o'zgaradi. T[K][] ni o'ngdan chapga o'qing: "T dagi K maydon turining massivi".

Tekshirib ko'ring: pluck(menu, "name") va menu.map((d) => d.name) — natija turlari farq qiladimi? Unda pluck nimaga kerak?

Javob

Farq qilmaydi — ikkalasi ham string[]. Funksiyaning foydasi boshqa: kalit qiymat sifatida uzatiladi. Uni foydalanuvchi tanlagan ustun nomidan, sozlamadan yoki boshqa funksiyadan olish mumkin. Cheklov (K extends keyof T) esa bunday dinamik kalit ham faqat mavjud maydon bo'lishini kafolatlaydi.

O'tgan darsdagi pair<T>(a: T, b: T) ikki xil turni qabul qilmasdi. Ikkita mustaqil tur parametri bilan bunday cheklov yo'q:

ts
function merge<A extends object, B extends object>(
  a: A,
  b: B,
): A & B {
  return { ...a, ...b };
}

const base = { name: "Osh", price: 35000 };
const dish = merge(base, { cookMinutes: 40 });
console.log(dish.name, dish.cookMinutes); // Osh 40

A va B alohida chiqariladi, natija esa ularning kesishmasi: A & B. extends object — "faqat obyektlar" (son yoki satr emas), spread obyekt bilan ishlashi uchun.

4.2 Argumentlar ro'yxati — tur parametri

Yana bir kuchli naqsh: funksiyaning butun argumentlar ro'yxatini tur parametri qilish. Masalan, berilgan funksiyani ikki marta chaqiradigan yordamchi:

ts
function callTwice<A extends unknown[]>(
  fn: (...args: A) => void,
  ...args: A
): void {
  fn(...args);
  fn(...args);
}

callTwice((dish: string, qty: number) => {
  console.log(`${dish} × ${qty}`);
}, "Osh", 2);

Konsolda:

text
Osh × 2
Osh × 2

A extends unknown[] — "A — qandaydir massiv (tuple)". fn ning parametrlaridan A = [dish: string, qty: number] chiqarildi. Endi ...args ham aynan shu tuple: "Osh", 2 dan boshqa narsa o'tmaydi:

ts
function callTwice<A extends unknown[]>(
  fn: (...args: A) => void,
  ...args: A
): void {
  fn(...args);
}
callTwice((dish: string, qty: number) => {
  console.log(dish, qty);
}, "Osh", "2");

Kompilyator:

text
TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

Tarjimasi: "string turidagi argumentni number turidagi parametrga berib bo'lmaydi". A tuple'ida ikkinchi o'rin — qty: number, biz esa "2" satrini berdik. vazifalar dagi kechiktir (debounce, 12-qismdan) aynan shu naqsh bilan yozilgan: kechiktirilgan funksiya asl funksiyaning argumentlarini aynan qabul qiladi.

5. Tuzoq: cheklov — bu "kamida", "aynan" emas

5.1 T ni o'zingiz yasay olmaysiz

Cheklovli T dan qiymat qaytaradigan funksiya yangi obyekt yasab qaytarmoqchi:

ts
function withDefaultName<T extends { name: string }>(item: T): T {
  return { name: "Nomsiz" };
}

Kompilyator:

text
TS2322: Type '{ name: string; }' is not assignable to type 'T'.
  '{ name: string; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ name: string; }'.

Tarjimasi: "{ name: string; } T ning cheklovga mos keladi, lekin T cheklovning boshqa kichik turi bilan to'ldirilishi mumkin". Oddiyroq: T — "kamida name bor narsa", lekin aniq nima ekanini chaqiruvchi hal qiladi. withDefaultName({ name: "Osh", price: 35000 }) chaqirilsa, T da price ham bor — { name: "Nomsiz" } esa unda yo'q. Funksiya T va'da berib, boshqa narsa qaytarardi.

Bu generic'lardagi eng ko'p uchraydigan xabarlardan biri. Tuzatish — T ni kiruvchi qiymatdan yasash: return { ...item, name: item.name || "Nomsiz" }; — bu T ning hamma maydonini saqlaydi.

5.2 TypeScript ushlamaydigan tuzoq: keyof — kalitlarning hammasi emas

keyof T — T turida e'lon qilingan kalitlar. Ishlash paytidagi obyektda esa ko'proq kalit bo'lishi mumkin (Strukturaviy tiplash). Index signature va Record darsida Object.keys nega string[] qaytarishini ko'rgandik. Agar buni as bilan "tuzatsak":

ts
type Prices = { osh: number; manti: number };

function printAll(p: Prices): void {
  for (const k of Object.keys(p) as (keyof Prices)[]) {
    console.log(k, p[k].toFixed(0));
  }
}

const fromMenu = { osh: 35000, manti: 30000, note: "yangi" };
printAll(fromMenu);

Konsolda:

text
osh 35000
manti 30000
TypeError: p[k].toFixed is not a function

tsc — xatosiz: fromMenu da osh va manti bor, ortiqcha note ga ruxsat. as (keyof Prices)[] esa "kalitlar faqat shu ikkita" deb yolg'on va'da berdi. Uchinchi aylanishda k = "note", p[k] — "yangi" satri, unda toFixed yo'q. Qoida: Object.keys(…) as (keyof T)[] — as ning odatdagi xavfi (Type assertion). Kalitlar ro'yxatini aniq yozing (["osh", "manti"] as const) yoki har qiymatni tekshiring.

6. Ko'p uchraydigan xatolar

6.1 Cheklov o'rniga oddiy parametr turi

longest(a: { length: number }, …) — natija aniq turni yo'qotadi. Tuzatish: longest<T extends { length: number }>(a: T, b: T): T.

6.2 Kalitga string turi

(obj: T, key: string) => obj[key] — TS7053. Tuzatish: <T, K extends keyof T>(obj: T, key: K): T[K].

6.3 T ni qo'lda yasash

return { name: "…" } (T kutilganda) — TS2322 … could be instantiated with a different subtype. Tuzatish: natijani kiruvchi qiymatdan spread bilan yasang yoki natija turini cheklovning o'zi qiling.

6.4 Object.keys(x) as (keyof T)[]

Ortiqcha kalitlar ham keladi. Tuzatish: aniq kalitlar ro'yxati yoki tekshiruv.

6.5 Cheklovni haddan ziyod qattiq qo'yish

<T extends { name: string; price: number; cookMinutes: number }> — funksiyaga faqat price kerak bo'lsa ham, ichimliklar o'tmay qoladi. Tuzatish: cheklovda faqat funksiya haqiqatan ishlatadigan maydonlar.

7. Mashqlar

1-mashq (oson): Natija turlari

ts
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
const table = { no: 7, waiter: "Sardor", vip: false };
const a = getProperty(table, "waiter");
const b = getProperty(table, "vip");
console.log(a, b); // Sardor false

a ning turi: [:string]. b ning turi: [:boolean]. keyof typeof table da nechta a'zo bor: .

Yechim
  • a — string: T[K], K = "waiter".
  • b — boolean: K = "vip". const bo'lsa ham obyekt maydonlari kengaygan (Tur chiqarish), shuning uchun false emas, boolean.
  • 3 ta: "no" | "waiter" | "vip".

2-mashq (o'rta): sumBy

Ro'yxatdagi obyektlarning sonli maydonini yig'adigan funksiya yozing: sumBy(menu, "price"). Maydon soni bo'lmasa (sumBy(menu, "name")), tsc to'xtatsin. Ishora: elementga cheklov qo'ying — T extends Record<K, number>: "K kalitida son bor obyekt" (Index signature va Record). K — string.

Yechim
ts
function sumBy<K extends string, T extends Record<K, number>>(
  items: readonly T[],
  key: K,
): number {
  let sum = 0;
  for (const item of items) sum += item[key];
  return sum;
}

const order = [
  { name: "Osh", price: 35000, qty: 2 },
  { name: "Ko'k choy", price: 5000, qty: 1 },
];
console.log(sumBy(order, "price"), sumBy(order, "qty")); // 40000 3
  • Cheklovlar tartibi muhim: T ning cheklovi K ga tayanadi, shuning uchun K birinchi e'lon qilingan.
  • item[key] — number: Record<K, number> shuni kafolatlaydi.
  • sumBy(order, "name") — TS2345: name satr, Record<"name", number> talabi bajarilmadi.

3-mashq (qiyin): updateField — o'zgarmas yangilash

Bron obyektining bitta maydonini yangi obyekt bilan almashtiradigan funksiya yozing: updateField(booking, "time", "20:00"). Asl obyekt o'zgarmasin. Talablar: kalit — faqat mavjud maydon; qiymat turi — shu maydon turi (people ga satr berib bo'lmasin).

Yechim
ts
function updateField<T extends object, K extends keyof T>(
  obj: T,
  key: K,
  value: T[K],
): T {
  return { ...obj, [key]: value };
}

type Booking = { guest: string; people: number; time: string };
const b: Booking = { guest: "Dilshod aka", people: 4, time: "19:00" };
const moved = updateField(b, "time", "20:00");
console.log(moved.time, b.time); // 20:00 19:00

Ikki noto'g'ri chaqiruv tsc da shunday to'xtaydi:

ts
function updateField<T extends object, K extends keyof T>(
  obj: T,
  key: K,
  value: T[K],
): T {
  return { ...obj, [key]: value };
}
type Booking = { guest: string; people: number; time: string };
const b: Booking = { guest: "Dilshod aka", people: 4, time: "19:00" };
updateField(b, "people", "5");
updateField(b, "table", 7);

Xato:

text
TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
TS2345: Argument of type '"table"' is not assignable to parameter of type 'keyof Booking'.
  • value: T[K] — qiymat turi kalitga bog'langan: K = "people" bo'lsa, number.
  • { ...obj, [key]: value } — spread va hisoblangan kalit: nusxa, bitta maydon almashgan. b.time o'zgarmadi.
  • Bu React holatini yangilashdagi naqshning o'zi (setState({ ...state, time })) — 17-qismda qayta uchratasiz.

4-mashq: Vazifalar qadami — generic modullar .ts ga

vazifalar dagi uch "sof" modulda (DOM'siz) 12-qismdan beri JSDoc @template bor: qidir (qidiruv), tartibla (saralash), kechiktir (debounce). Ular — generic cheklovlarning tayyor misoli. Endi .ts ga o'tadi.

  • Branch: refactor/generics
  • Commit: refactor: qidiruv, saralash, taymer — generic cheklovlar bilan .ts, tanasi: @template o'rniga <T extends …>; Oraliq — nomlangan tuple
  1. git mv bilan qidiruv.js, saralash.js, taymer.js → .ts. npx tsc --noEmit — nechta xato?
  2. @template larni TypeScript cheklovlariga o'tkazing:
    • qidir<T extends { readonly matn: string }>(vazifalar: readonly T[], sorov: string): T[]
    • tartibla<T extends VazifaMalumoti>(vazifalar: readonly T[], saralash: Saralash): T[]
    • kechiktir<A extends unknown[]>(ishlovchi: (...argumentlar: A) => void, kutish: number)
  3. Import yo'llari (asosiy.js, holat.ts, marshrut.ts, render.js, royxat.ts, 3 ta test) va sw.js da VERSIYA = "v5-5".
  4. Tajriba: qidir ni generic'siz yozib ko'ring — { readonly matn: string }[] qabul qilib, shuni qaytarsin. tsc nima deydi?
Yechim

1. Nomi o'zgartirilganda — 34 ta xato:

3 generic modul .ts ga: 34 xato kodlar bo'yicha
  • TS7006 (any parametr)24 xato
  • TS7005 (any o'zgaruvchi)5 xato
  • TS7034 (any joylarda)2 xato
  • TS7019 (any[] rest)1 xato
  • TS7031 (any destructuring)1 xato
  • TS23221 xato

Manba: O'lchandi: tsc 7.0.2 --noEmit, vazifalar holatlar/18 → qidiruv/saralash/taymer .ts, Node 24.21.0, 2026-10-06

Ko'pi — yashirin any: JSDoc turlari .ts da o'qilmaydi (Klasslar TypeScript'da qadamidagi kabi). taymer.ts dagilar yangi xil:

text
assets/js/taymer.ts(13,7): error TS7034: Variable 'taymer' implicitly has type 'any' in some locations where its type cannot be determined.
assets/js/taymer.ts(35,27): error TS7019: Rest parameter 'argumentlar' implicitly has an 'any[]' type.

let taymer; — qiymatsiz e'lon, JSDoc turi esa e'tiborsiz. ...argumentlar — rest parametr, turi any[].

2. Generic cheklovlar bilan — taymer.ts:

ts
// taymer.ts — tez-tez keladigan chaqiruvlarni kamaytirish

// (kanondagi JSDoc izohlari qisqartirildi)
export type Kechiktirilgan<A extends unknown[]> = ((
  ...argumentlar: A
) => void) & {
  hoziroq(): void; // kutayotgan chaqiruvni hoziroq bajaradi
  bekor(): void; // kutayotgan chaqiruvni tashlab yuboradi
};

export function kechiktir<A extends unknown[]>(
  ishlovchi: (...argumentlar: A) => void,
  kutish: number,
): Kechiktirilgan<A> {
  let taymer: ReturnType<typeof setTimeout> | undefined;
  // kutayotgan chaqiruv argumentlari
  let kutayotgan: A | null = null;

  function hoziroq(): void {
    clearTimeout(taymer);
    if (kutayotgan === null) {
      return;
    }
    const argumentlar = kutayotgan;
    kutayotgan = null;
    ishlovchi(...argumentlar);
  }

  function bekor(): void {
    clearTimeout(taymer);
    kutayotgan = null;
  }

  function kechiktirilgan(...argumentlar: A): void {
    kutayotgan = argumentlar;
    clearTimeout(taymer);
    taymer = setTimeout(hoziroq, kutish);
  }

  return Object.assign(kechiktirilgan, { hoziroq, bekor });
}
  • A extends unknown[] — «Argumentlar ro'yxati — tur parametri» bo'limidagi callTwice naqshi. Qidiruv maydonidagi kechiktir((matn: string) => …, 300) dan A = [matn: string] chiqariladi.
  • Kechiktirilgan<A> — generic tur (type ham tur parametri oladi — Generic interface, type va klass darsida). U kesishma: "shu argumentli funksiya va hoziroq, bekor metodlari".
  • ReturnType<typeof setTimeout> — "setTimeout qaytaradigan tur": brauzerda son, Node'da obyekt. Tayyor yordamchi tur — Utility types darsida.

qidiruv.ts dagi qidir:

ts
export function qidir<T extends { readonly matn: string }>(
  vazifalar: readonly T[],
  sorov: string,
): T[] {
  const toza = normalla(sorov);
  if (toza === "") {
    return [...vazifalar];
  }
  return vazifalar.filter((v) => normalla(v.matn).includes(toza));
}

Cheklovda faqat matn — funksiya boshqa maydonni ishlatmaydi («Cheklovni haddan ziyod qattiq qo'yish» qoidasi). Shu faylda Massiv va tuple turlari darsida ko'rgan export type Oraliq = [boshi: number, oxiri: number]; ham paydo bo'ldi.

4. Generic nega kerak — qidir generic'siz bo'lsa (haqiqiy tsc chiqishi):

text
assets/js/holat.ts(67,3): error TS2322: Type '{ readonly matn: string; }[]' is not assignable to type 'Vazifa[]'.
  Type '{ readonly matn: string; }' is missing the following properties from type 'Vazifa': id, bajarildi, #matn, matnniOzgartir, and 2 more.

«Nega bu kerak?» bo'limidagi longest holatining o'zi, endi haqiqiy loyihada. holat.ts dagi korinadiganlar(): Vazifa[] qidir natijasini qaytaradi. Generic'siz natija "matni bor nimadir" bo'lib qoldi — id, bajarildi, metodlar yo'qoldi.

saralash.ts da yana bir nozik joy bor: type Taqqoslovchi = (a: VazifaMalumoti, b: VazifaMalumoti) => number. Vazifa klassi obyektlari ham unga mos, chunki ularda id, matn (getter) va bajarildi bor — Strukturaviy tiplash. Klassni import qilish shart emas.

Commit va PR:

bash
git switch -c refactor/generics
git mv assets/js/qidiruv.js assets/js/qidiruv.ts
git mv assets/js/saralash.js assets/js/saralash.ts
git mv assets/js/taymer.js assets/js/taymer.ts
# cheklovlar, import yo'llari, sw.js VERSIYA
npm run build && npm test && npm run tip
npm run lint && npm run format:check
git add .
git commit -m "refactor: qidiruv, saralash, taymer — generic cheklovlar bilan .ts" \
  -m "@template o'rniga <T extends …>; Oraliq — nomlangan tuple"
git push -u origin refactor/generics
gh pr create --fill
gh pr merge --merge

Commit sarlavhasi — 68 belgi. Natija: npm test — 137/137, tip, lint, format:check — toza. Brauzer tekshiruvi (dist/) — 159/159. Diff: 13 fayl, +101 −110.

8. Real ishda

  • Kutubxona API'lari cheklovlarga to'la: useState<S>, ORM'larda findOne<T extends Entity>, lodash'da pick<T, K extends keyof T>. K extends keyof T naqshini hujjatlarda har kuni ko'rasiz.
  • Forma va jadval komponentlari: <Table<Row> columns={…} /> da ustun kaliti keyof Row — noto'g'ri ustun nomi yozish paytida ushlanadi.
  • Debounce, throttle, memoize — A extends unknown[] naqshi: o'ralgan funksiya aslining argumentlarini saqlaydi.
  • Intervyu: "T extends nima qiladi?", "getProperty<T, K extends keyof T> yozing", "could be instantiated with a different subtype nimani anglatadi?" — middle darajadagi klassik savollar.

Xulosa

  • T extends Shakl — generic'ga "kamida shu shakl" talabi: ichkarida Shakl maydonlari ishlatiladi, tashqarida aniq T qaytadi.
  • keyof T — kalitlar union'i. K extends keyof T — faqat mavjud kalit, T[K] — shu kalitning turi.
  • Bir nechta tur parametri bir-biriga tayanishi mumkin (K extends keyof T, T extends Record<K, number>).
  • A extends unknown[] — argumentlar ro'yxatini (tuple) tur parametri qilish.
  • Cheklov — "kamida", "aynan" emas: T ni qo'lda yasab bo'lmaydi (TS2322 … different subtype), keyof T esa ishlash paytidagi hamma kalitlar emas.

Keyingi dars: Generic interface, type va klass — ApiResponse<T>, generic konteyner klass va standart tur parametri.

Manbalar

  • TypeScript Handbook: "Generics" (Generic Constraints, Using Type Parameters in Generic Constraints) — typescriptlang.org/docs/handbook/2/generics.html#generic-constraints
  • TypeScript Handbook: "Keyof Type Operator" — typescriptlang.org/docs/handbook/2/keyof-types.html
  • TypeScript Handbook: "Indexed Access Types" — typescriptlang.org/docs/handbook/2/indexed-access-types.html
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
TypeScript generic cheklovlar va keyof: T extends … va K extends keyof T — IlmHamroh