Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Rekursiv tur: o'zini ishlatadigan tur
- 2.1 JSONValue
- 2.2 JSON'ga o'tmaydigan narsalar
- 2.3 type mos, interface — yo'q
- 3. DeepPartial: har qavatni ixtiyoriy qilish
- 3.1 Yozuv
- 3.2 DeepPartial ning chegaralari
- 4. Rekursiv conditional type
- 4.1 Yo'l parametrlari — barchasi
- 4.2 Siz allaqachon ko'rgan rekursiv tur
- 5. Kompilyator chegaralari
- 5.1 Chuqurlik: TS2589
- 5.2 Kenglik: TS2590
- 5.3 Narx: sekinlik
- 6. Qachon to'xtash kerak
- 7. TypeScript ushlamaydigan tuzoq: JSONValue ham NaN ni o'tkazadi
- 8. Ko'p uchraydigan xatolar
- 8.1 Ichma-ich obyektga Partial
- 8.2 JSON ma'lumotini interface bilan yozish
- 8.3 To'xtash sharti yo'q rekursiya
- 8.4 Juda katta union
- 8.5 DeepPartial<Date>
- 9. Mashqlar
- 1-mashq (oson): Qavatlarni sanang
- 2-mashq (o'rta): DeepReadonly
- 3-mashq (qiyin): Amaliy tajriba — sozlamalarni yangilash
- 10. Real ishda
- Xulosa
- Manbalar
TypeScript rekursiv turlar: DeepPartial, JSONValue va kompilyator chegaralari
Qisqacha: Rekursiv tur — o'zini o'zi ishlatadigan tur:
JSONValueichida yanaJSONValue[],DeepPartial<T>ichida yanaDeepPartial<T[K]>. Ular ichma-ich tuzilmalarni (sozlamalar, JSON, menyu daraxti) tasvirlaydi. Lekin kompilyatorning chegarasi bor: chuqur rekursiyaTS2589("excessively deep"), juda katta union —TS2590beradi. Murakkab tur kodni sekinlashtiradi va o'qib bo'lmas qiladi — qayerda to'xtashni bilish ham ko'nikma.
Bu darsda
JSONValueni yozib, qaysi qiymatlar JSON'ga xavfsiz o'tishini turda ifodalaysiz.DeepPartialbilan ichma-ich sozlamalarni qisman yangilaysiz va uning chegaralarini (massiv,Date) bilasiz.- Rekursiv conditional type bilan yo'l parametrlarini chiqarasiz:
"/menu/:dishId/size/:size"→"dishId" | "size". TS2589vaTS2590xatolarini tushuntira olasiz va kompilyator chegaralarini raqam bilan bilasiz.- Qachon murakkab tur yozmaslik kerakligini ayta olasiz.
Oldin bilishingiz kerak: Branded types va variance, infer kalit so'zi, Rekursiya asoslari.
1. Nega bu kerak?
O'tgan darslarda turlarni boshqa turlardan hisobladik: mapped, conditional, infer, shablon. Hammasi bir qavatli edi. Hayotdagi ma'lumot esa ko'pincha ichma-ich: obyekt ichida obyekt, uning ichida massiv. Bu dars — tur darajasidagi "sehr" qismining oxirgisi va eng chuquri. Uning yarmi — "kutubxona muallifi darajasi", lekin chegaralar haqidagi qismi har bir dasturchiga kerak.
«Bahor» admin panelida sozlamalar bor:
type Settings = {
hours: { open: number; close: number };
booking: { from: number; to: number; maxPeople: number };
theme: "yorug'" | "qorong'i";
};
const settings: Settings = {
hours: { open: 7, close: 23 },
booking: { from: 10, to: 22, maxPeople: 12 },
theme: "yorug'",
};
console.log(settings.booking.maxPeople); // 12Jasur aka bitta narsani o'zgartirmoqchi: bronda bir stolga ko'pi bilan 8 kishi. Sardor utility types darsidagi Partial ni oldi:
type Settings = {
hours: { open: number; close: number };
booking: { from: number; to: number; maxPeople: number };
};
const patch: Partial<Settings> = { booking: { maxPeople: 8 } };Kompilyator:
TS2739: Type '{ maxPeople: number; }' is missing the following properties from type '{ from: number; to: number; maxPeople: number; }': from, to"{ maxPeople } turida from va to yo'q". Partial faqat birinchi qavatni ixtiyoriy qiladi: booking ni berish shart emas, lekin bersangiz — to'liq. Ichki qavatlarga ham ? qo'yish kerak, ularning ichidagilariga ham — necha qavat bo'lsa, shuncha. Buni bitta tur bilan aytish uchun tur o'zini o'zi chaqirishi kerak.
2. Rekursiv tur: o'zini ishlatadigan tur
2.1 JSONValue
Rekursiv tur (recursive type) — ta'rifida o'zining nomi uchraydigan tur. Bu rekursiv funksiya ning tur darajasidagi egizagi. Kunlik o'xshatish: matryoshka. "Matryoshka — bu qo'g'irchoq, ichida yana matryoshka bo'lishi mumkin". Ta'rif o'zini ishlatadi, lekin oxiri bor — eng kichigi bo'sh.
JSON ma'lumoti aynan shunday tuzilgan. JSON asoslari darsidan: JSON'da satr, son, mantiqiy qiymat, null, massiv va obyekt bo'ladi — massiv va obyekt ichida esa yana shularning istalgani:
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
const saved: JSONValue = {
name: "Osh",
prices: [35000, 30000],
tags: { spicy: false, note: null },
};
console.log(JSON.stringify(saved).length); // 72Kompilyator saved ni tekshirganda turni kerak bo'lgan chuqurlikkacha ochadi:
| Qavat | Qiymat | Qaysi a'zo mos keladi |
|---|---|---|
| 1 | butun obyekt | { [key: string]: JSONValue } |
| 2 | "Osh" |
string |
| 2 | [35000, 30000] |
JSONValue[] |
| 3 | 35000 |
number |
| 2 | { spicy: false, note: null } |
{ [key: string]: JSONValue } |
| 3 | false, null |
boolean, null |
Ichma-ichlik qancha chuqur bo'lsa, tekshiruv shuncha qavat tushadi. Qiymat chekli — tekshiruv ham chekli.
2.2 JSON'ga o'tmaydigan narsalar
JSONValue ning foydasi — u JSON'ga jim buziladigan qiymatlarni rad etadi:
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
const a: JSONValue = { savedAt: new Date() };
const b: JSONValue = { note: undefined };
const c: JSONValue = { print: () => "osh" };Kompilyator:
TS2322: Type '{ savedAt: Date; }' is not assignable to type 'JSONValue'.
Types of property 'savedAt' are incompatible.
Type 'Date' is not assignable to type 'JSONValue | undefined'.
Type 'Date' is not assignable to type '{ [key: string]: JSONValue; }'.
Index signature for type 'string' is missing in type 'Date'.
TS2322: Type '{ note: undefined; }' is not assignable to type 'JSONValue'.
Type '{ note: undefined; }' is not assignable to type 'null'.
TS2322: Type '{ print: () => string; }' is not assignable to type 'JSONValue'.
Types of property 'print' are incompatible.
Type '() => string' is not assignable to type 'JSONValue | undefined'.Har biri haqiqiy JSON tuzog'i (JSON chuqur):
Date—JSON.stringifyuni satrga aylantiradi,JSON.parseesaDateqilib qaytarmaydi. Zanjirning oxirgi qatori: "Dateda satr kalitli indeks imzosi yo'q".undefined— kalit butunlay yo'qoladi. Ikkinchi zanjir qisqa: kompilyator union a'zolarini sinab, oxirgi sinagani (null) bilan xabar berdi.- Funksiya — u ham jim tashlab yuboriladi.
2.3 type mos, interface — yo'q
Kutilmagan detal. Xuddi shu shakl interface bilan yozilsa:
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
interface Hours {
open: number;
close: number;
}
const hours: Hours = { open: 7, close: 23 };
const json: JSONValue = hours;Kompilyator:
TS2322: Type 'Hours' is not assignable to type 'JSONValue'.
Type 'Hours' is not assignable to type '{ [key: string]: JSONValue; }'.
Index signature for type 'string' is missing in type 'Hours'.Sababi: type alias'ga TypeScript yashirin indeks imzosini beradi, interface ga — yo'q. Interface keyinchalik declaration merging bilan kengayishi mumkin, shuning uchun kompilyator "hamma kalitlar JSONValue" deb kafolat bera olmaydi. Kanon vazifalar loyihasida ham xuddi shu sabab bilan interface VazifaMalumoti dan type ga o'tilgan edi (type va interface qadami, TS2677). Qoida: JSON'ga saqlanadigan ma'lumot shakllarini type bilan yozing.
Tekshirib ko'ring:
const x: JSONValue = [1, [2, [3, ["to'rt"]]]];—tsco'tkazadimi?
Javob
O'tkazadi. Har qavatda massiv — JSONValue[], uning elementlari — son yoki yana JSONValue[], eng ichidagisi — satr. Chuqurlik qancha bo'lsa ham, har qavat bir xil qoidaga bo'ysunadi.
3. DeepPartial: har qavatni ixtiyoriy qilish
3.1 Yozuv
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
type Settings = {
hours: { open: number; close: number };
booking: { from: number; to: number; maxPeople: number };
};
const patch: DeepPartial<Settings> = { booking: { maxPeople: 8 } };
console.log(patch.booking?.maxPeople); // 8Mapped type — Partial ning o'zi, faqat qiymat turi T[K] emas, DeepPartial<T[K]>. Kompilyator uni dangasa (lazily) ochadi: faqat kerak bo'lgan qavatni.
| Qadam | Tur | Natija |
|---|---|---|
| 1 | DeepPartial<Settings> |
{ hours?: DeepPartial<…>; booking?: DeepPartial<…> } |
| 2 | booking kerak bo'ldi → DeepPartial<{ from; to; maxPeople }> |
{ from?: …; to?: …; maxPeople?: … } |
| 3 | maxPeople → DeepPartial<number> |
number |
Uchinchi qadam muhim: DeepPartial<number> — number. Mapped type'ga oddiy (primitiv) tur berilsa, u o'zgarishsiz qaytadi — rekursiya shu yerda to'xtaydi. Bu matryoshkaning "eng kichigi".
3.2 DeepPartial ning chegaralari
Bizning DeepPartial — o'quv nusxasi. Ikki holatda u kutilmagan natija beradi (hover bilan tekshirildi):
| Tur | DeepPartial natijasi |
Muammo |
|---|---|---|
{ name: string }[] |
(DeepPartial<{ name: string }> | undefined)[] |
massiv elementlari ham undefined bo'la oladi |
Date |
{ toString?: …; getTime?: …; … 38 more … } |
sana — metodlar to'plamiga aylandi |
Massivda "elementni qisman yangilash" ma'nosiz: ro'yxat odatda butunligicha almashtiriladi. Date, Map, funksiya — ularni ichidan ochish umuman kerak emas. Kutubxonalardagi tayyor versiyalar (masalan, type-fest dagi PartialDeep) bu holatlarni conditional type bilan alohida ko'radi va 30–60 qatordan iborat. Bu — kutubxona muallifi darajasi: ilova kodida o'zingiz yozmang, tayyorini oling (best practices darsida type-fest haqida).
4. Rekursiv conditional type
4.1 Yo'l parametrlari — barchasi
Template literal types darsida bitta parametrni chiqargan edik va "ko'pi — keyinroq" degandik. Mana o'sha joy:
type Params<T extends string> =
T extends `${string}:${infer P}/${infer Rest}`
? P | Params<Rest>
: T extends `${string}:${infer P}`
? P
: never;
const key: Params<"/menu/:dishId/size/:size"> = "size";
console.log(key); // sizeParams<"/menu/:dishId/size/:size"> — "dishId" | "size". Kompilyator satrni bo'lakma-bo'lak "yeydi":
| Qadam | T |
Qaysi shox | Natija |
|---|---|---|---|
| 1 | "/menu/:dishId/size/:size" |
1-qolip: P = "dishId", Rest = "size/:size" |
"dishId" | Params<"size/:size"> |
| 2 | "size/:size" |
1-qolip mos emas (: dan keyin / yo'q), 2-qolip: P = "size" |
"size" |
| 3 | yig'adi | — | "dishId" | "size" |
Bu tur amalda nima beradi? Parametrlar nomidan obyekt turi yasash mumkin — Record bilan:
type Params<T extends string> =
T extends `${string}:${infer P}/${infer Rest}`
? P | Params<Rest>
: T extends `${string}:${infer P}`
? P
: never;
function buildPath<T extends string>(
pattern: T,
params: Record<Params<T>, string>,
): string {
let path: string = pattern;
for (const [name, value] of Object.entries<string>(params)) {
path = path.replace(`:${name}`, value);
}
return path;
}
const url = buildPath("/menu/:dishId/size/:size", {
dishId: "osh",
size: "katta",
});
console.log(url); // /menu/osh/size/kattaparams turi — Record<"dishId" | "size", string>, ya'ni { dishId: string; size: string }. Bitta parametrni unutsangiz yoki nomini xato yozsangiz (dishID), tsc darhol to'xtatadi. Yo'l shablonini o'zgartirsangiz — chaqiruvlar o'zi tekshiriladi. «Bahor» saytidagi har bir havola shunday yasalsa, "sahifa topilmadi" xatolari kamayadi.
Har qadamda satr qisqaradi — rekursiya albatta tugaydi. Bu rekursiv funksiyaning asosiy qoidasi (Rekursiya asoslari): har chaqiruvda masala kichrayishi va to'xtash sharti bo'lishi kerak. Bu yerda to'xtash sharti — oxirgi shox never (parametr qolmadi).
4.2 Siz allaqachon ko'rgan rekursiv tur
infer darsidagi Awaited ham rekursiv: Promise ichida Promise bo'lsa, yana ochadi. Uning soddalashtirilgan ko'rinishi — ichma-ich massivni tekislash:
type Flatten<T> = T extends readonly (infer E)[] ? Flatten<E> : T;
const n: Flatten<number[][][]> = 35000;
console.log(n); // 35000number[][][] → number[][] → number[] → number: har qadamda bir qavat ochildi, massiv bo'lmay qolganda — to'xtadi.
Tekshirib ko'ring:
Params<"/menu">nima beradi? Nega?
Javob
never. Satrda : yo'q — ikkala qolip ham mos kelmaydi, oxirgi shox never ishlaydi. Bu "parametrsiz yo'l" uchun to'g'ri javob: hech qanday parametr kalit yo'q.
5. Kompilyator chegaralari
Rekursiv turlar kuchli, lekin kompilyator ularni cheksiz hisoblamaydi. Uch chegara bor — ularni TypeScript 7.0.2 da o'zimiz sinab ko'rdik.
5.1 Chuqurlik: TS2589
Satrni N marta takrorlaydigan tur:
type Repeat<
S extends string,
N extends number,
A extends S[] = [],
> = A["length"] extends N ? A : Repeat<S, N, [...A, S]>;
type Ok = Repeat<"osh", 999>;
type TooDeep = Repeat<"osh", 1000>;Kompilyator:
TS2589: Type instantiation is excessively deep and possibly infinite.Tarjimasi: "Turni hisoblash haddan tashqari chuqur va ehtimol cheksiz". Repeat<"osh", 999> — o'tdi, 1000 — xato. A — har qadamda bitta element qo'shiladigan tuple ("akkumulyator"), uning uzunligi N ga yetganda to'xtaydi. Kichik son bilan, masalan Repeat<"osh", 2>:
| Qadam | A |
A["length"] extends 2? |
Keyingisi |
|---|---|---|---|
| 1 | [] |
0 — yo'q |
Repeat<"osh", 2, ["osh"]> |
| 2 | ["osh"] |
1 — yo'q |
Repeat<"osh", 2, ["osh", "osh"]> |
| 3 | ["osh", "osh"] |
2 — ha |
natija: ["osh", "osh"] |
N = 1000 da kompilyator shu jadvalning 1000-qatoriga yetolmaydi.
Bu tur "dumli" (tail) rekursiya: rekursiv chaqiruvning natijasi o'zgarishsiz qaytariladi (? A : Repeat<…>). Bunday turlar uchun TypeScript 4.5 dan beri chegara — 1000 qadam. Natija ustiga nimadir qo'shilsa (masalan, 1 | Len<R>), chegara ancha past. Bizning sinovda bunday tur 48 qavatda ishladi, 49 da — xuddi shu TS2589.
| Rekursiya turi | Misol | Biz o'lchagan chegara |
|---|---|---|
| dumli | ? A : Repeat<S, N, [...A, S]> |
999 — o'tdi, 1000 — TS2589 |
| dumsiz | ? 1 | Len<R> : 0 |
48 — o'tdi, 49 — TS2589 |
Cheksiz rekursiya ham shu xato bilan to'xtaydi — kompilyator "osilib" qolmaydi.
5.2 Kenglik: TS2590
Template literal types darsida juftliklar ko'paytma bilan o'sishini ko'rdik:
type D = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type FiveDigits = `${D}${D}${D}${D}${D}`;Kompilyator:
TS2590: Expression produces a union type that is too complex to represent."Ifoda ko'rsatib bo'lmaydigan darajada murakkab union turini hosil qiladi". Besh xonali kodlar — 100 000 ta satr. To'rt xonali (10 000 ta) — hali o'tadi.
5.3 Narx: sekinlik
Xato bermagan tur ham bepul emas. Biz to'rt xil o'lchamdagi kod union'ini va undan mapped type bilan yasalgan ishlovchilar turini tekshirib, tsc --extendedDiagnostics hisobotidan kompilyator yaratgan turlar sonini oldik:
- 10 ta kodtekshiruv ~0,04 s7 856 ta tur
- 100 ta kod~0,04 s8 047 ta tur
- 1 000 ta kod~0,05 s9 947 ta tur
- 10 000 ta kod~0,12 s, xotira +50 %28 947 ta tur
Manba: O'lchandi: tsc 7.0.2 --extendedDiagnostics (Types, Check time — 7 marta medianasi), Windows 11, 2026-10-06
Taxminan 7 800 tasi — standart kutubxona turlari (har faylda bor). 1 000 tagacha farq sezilmaydi. 10 000 da — turlar uch barobar, tekshiruv vaqti ham taxminan uch barobar. Bitta fayldagi bitta tur uchun 0,1 soniya kam ko'rinadi. Lekin muharrir har tugma bosilganda qayta tekshiradi, katta loyihada esa bunday turlar o'nlab. Natija — sekin avtoto'ldirish va kechikkan qizil chiziqlar.
6. Qachon to'xtash kerak
Tur darajasidagi dasturlash o'ziga tortadi: "buni ham turda ifodalasam-chi?". Mana amaliy qoidalar:
Avval bir tamoyil: tur — kod o'quvchisi uchun hujjat ham. Uni siz bir marta yozasiz, boshqalar esa yuz marta o'qiydi: hover'da, xato xabarida, kod ko'rib chiqishda. Murakkab tur yozish vaqtingizni emas, ularning vaqtini sarflaydi.
- Tur ma'lumotni tasvirlasin, hisob-kitob qilmasin.
JSONValue,DeepPartial,Params— ma'lumot shakli. Tur ichida qo'shish, saralash, satrni parse qilish — o'yin, ish emas. - Hover'da o'qib bo'lmasa — juda murakkab. Hamkasbingiz xato xabarini tushunmasa, tur unga yordam bermayapti.
- Tayyorini oling.
type-fest, kutubxonalarning o'z turlari — sinalgan, chegaraviy holatlari hal qilingan. - Chegara yaqinida — boshqa yo'l.
TS2589yokiTS2590ni ko'rsangiz, odatda yechim — turni soddalashtirish,// @ts-ignoreemas. (// @ts-ignore— "keyingi qatordagi xatoni ko'rsatma" degan izoh; uni JavaScript'dan TypeScript'ga ko'chish darsida ko'ramiz. U muammoni yashiradi, hal qilmaydi.) - Tur — tekshiruv o'rnini bosmaydi. Keyingi bo'lim — shu haqda.
7. TypeScript ushlamaydigan tuzoq: JSONValue ham NaN ni o'tkazadi
JSONValue Date va undefined ni ushladi. Lekin uning number a'zosi hamma sonlarni qabul qiladi:
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
const settings: JSONValue = { price: NaN };
const text = JSON.stringify(settings);
const back = JSON.parse(text) as { price: number };
console.log(back.price.toFixed(0));tsc — xatosiz. Node esa:
TypeError: Cannot read properties of null (reading 'toFixed')JSON.stringify NaN va Infinity ni null ga aylantiradi — JSON'da ular yo'q. Turda esa NaN — number, number esa JSONValue ga kiradi. TypeScript'da "chekli son" degan tur yo'q: NaN ni turlar bilan ajratib bo'lmaydi. as { price: number } esa qaytgan qiymatni tekshirmasdan qabul qildi.
Himoya: saqlashdan oldin Number.isFinite bilan tekshiring; o'qishda esa JSON.parse natijasini unknown deb oling va shaklini tekshiring (API ma'lumotlarini tiplash darsida to'liq).
8. Ko'p uchraydigan xatolar
8.1 Ichma-ich obyektga Partial
TS2739: … is missing the following properties …: from, to — Partial bir qavatli. Tuzatish: DeepPartial (yoki ichki turni alohida Partial qiling).
8.2 JSON ma'lumotini interface bilan yozish
Index signature for type 'string' is missing in type 'Hours'. Tuzatish: type Hours = { … }.
8.3 To'xtash sharti yo'q rekursiya
TS2589: Type instantiation is excessively deep and possibly infinite. Har qadamda tur kichraymasa yoki oxirgi shox bo'lmasa — cheksiz. Tuzatish: har chaqiruvda nima kichrayishini tekshiring (satr qisqaradimi, tuple'ga element qo'shiladimi).
8.4 Juda katta union
TS2590: Expression produces a union type that is too complex to represent. Tuzatish: kombinatsiyani turga emas, ma'lumotga qo'ying yoki ${number} kabi naqsh turini ishlating.
8.5 DeepPartial<Date>
Sana metodlar to'plamiga aylanadi. Tuzatish: sanani sozlamalarda satr ("2026-10-06") yoki son sifatida saqlang yoki tayyor PartialDeep ni oling.
9. Mashqlar
1-mashq (oson): Qavatlarni sanang
type Flatten<T> = T extends readonly (infer E)[] ? Flatten<E> : T;
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
const a: Flatten<string[][]> = "osh";
const b: DeepPartial<{ hours: { open: number } }> = { hours: {} };
console.log(a, Object.keys(b)); // osh [ 'hours' ]Flatten<string[][]> — [:string]. Flatten bu yerda o'zini necha marta chaqirdi (birinchi chaqiruvdan tashqari)? [:2]. DeepPartial<number> — .
Yechim
string[][] → Flatten<string[]> (1-chaqiruv) → Flatten<string> (2-chaqiruv) → string massiv emas, to'xtaydi. DeepPartial primitiv turni o'zgartirmaydi — number. { hours: {} } ham to'g'ri: hours ichidagi open ham ixtiyoriy.
2-mashq (o'rta): DeepReadonly
DeepReadonly<T> yozing: har qavatdagi har maydon readonly. Sozlamalar obyektida settings.booking.maxPeople = 20 xato bersin. Ishora: DeepPartial dagi ? o'rniga oldiga readonly.
Yechim
type DeepReadonly<T> = {
readonly [K in keyof T]: DeepReadonly<T[K]>;
};
type Settings = {
booking: { from: number; to: number; maxPeople: number };
};
const settings: DeepReadonly<Settings> = {
booking: { from: 10, to: 22, maxPeople: 12 },
};
settings.booking.maxPeople = 20;Kompilyator:
TS2540: Cannot assign to 'maxPeople' because it is a read-only property.Tarjimasi: "maxPeople ga yozib bo'lmaydi, chunki u faqat o'qish uchun" — ikkinchi qavatda ham. Oddiy Readonly<Settings> faqat settings.booking = … ni taqiqlardi, settings.booking.maxPeople = 20 esa o'tib ketardi. Eslatma: bu ham faqat tur — ishlash paytida himoya uchun Object.freeze ni har qavatga qo'llash kerak.
3-mashq (qiyin): Amaliy tajriba — sozlamalarni yangilash
kurs/mashqlar/15/32-recursive/settings.ts faylini yarating:
JSONValue,DeepPartial<T>— darsdagidek.Settings(typebilan!) vaDEFAULTS— «Nega bu kerak?» bo'limidagi qiymatlar.applySettings(base, patch)—patch: DeepPartial<Settings>, yangiSettingsqaytaradi; asl obyekt o'zgarmaydi;undefinedqiymat eskisini o'chirmaydi (Mapped types dagi tuzoq!).toJSON(value: JSONValue)—JSON.stringifyning tur bilan himoyalangan o'rami.
Ishora: ichma-ich birlashtirishni turlarsiz yordamchi funksiya qiladi — mergeDeep(base: unknown, patch: unknown): unknown. Ikkalasi ham oddiy obyekt bo'lsa — kalitma-kalit o'zini chaqiradi, aks holda — patch (u undefined bo'lsa — base). applySettings uning natijasini as Settings bilan qaytaradi — bu "tekshiruvsiz joy", uni testlar himoya qiladi.
Avval applySettings parametrini Partial<Settings> qilib, testlarni yozing va npx tsc --pretty false ni ishga tushiring.
Yechim
Partial bilan testlardagi ikki chaqiruv to'xtadi:
32-recursive/settings.test.ts(7,42): error TS2739: Type '{ maxPeople: number; }' is missing the following properties from type '{ from: number; to: number; maxPeople: number; }': from, to
32-recursive/settings.test.ts(13,29): error TS2741: Property 'open' is missing in type '{ close: number; }' but required in type '{ open: number; close: number; }'.// 32-recursive/settings.ts — «Bahor» sozlamalari
export type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
export type Settings = {
hours: { open: number; close: number };
booking: { from: number; to: number; maxPeople: number };
theme: "yorug'" | "qorong'i";
};
export const DEFAULTS: Settings = {
hours: { open: 7, close: 23 },
booking: { from: 10, to: 22, maxPeople: 12 },
theme: "yorug'",
};
function isPlainObject(
value: unknown,
): value is Record<string, unknown> {
if (typeof value !== "object" || value === null) return false;
return !Array.isArray(value);
}
// Ichma-ich obyektlarni birlashtiradi; undefined qiymat — e'tiborsiz
function mergeDeep(base: unknown, patch: unknown): unknown {
if (!isPlainObject(base) || !isPlainObject(patch)) {
return patch === undefined ? base : patch;
}
const result: Record<string, unknown> = { ...base };
for (const [key, value] of Object.entries(patch)) {
result[key] = mergeDeep(base[key], value);
}
return result;
}
export function applySettings(
base: Settings,
patch: DeepPartial<Settings>,
): Settings {
// mergeDeep turlarni bilmaydi — natija shaklini testlar tekshiradi
return mergeDeep(base, patch) as Settings;
}
export function toJSON(value: JSONValue): string {
return JSON.stringify(value);
}mergeDeep— oddiy rekursiv funksiya (Rekursiya asoslari),DeepPartial— uning tur darajasidagi egizagi. Ikkalasi bir xil chuqurlikka tushadi.{ ...base }— har qavatda yangi obyekt: aslDEFAULTSo'zgarmaydi.toJSON(DEFAULTS)tscdan o'tadi, chunkiSettings—type.interfacebo'lganda — «typemos,interface— yo'q» bo'limidagi xato.
// 32-recursive/settings.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { DEFAULTS, applySettings, toJSON } from "./settings.ts";
test("applySettings: faqat berilgan maydon o'zgaradi", () => {
const next = applySettings(DEFAULTS, { booking: { maxPeople: 8 } });
assert.deepEqual(next.booking, { from: 10, to: 22, maxPeople: 8 });
assert.deepEqual(next.hours, DEFAULTS.hours);
});
test("applySettings: asl sozlama o'zgarmaydi", () => {
applySettings(DEFAULTS, { hours: { close: 22 } });
assert.equal(DEFAULTS.hours.close, 23);
});
test("applySettings: undefined eski qiymatni o'chirmaydi", () => {
const next = applySettings(DEFAULTS, { theme: undefined });
assert.equal(next.theme, "yorug'");
});
test("toJSON: sozlama JSON'ga aylanadi va qaytadi", () => {
const text = toJSON(DEFAULTS);
assert.deepEqual(JSON.parse(text), DEFAULTS);
});DeepPartial ga qaytarilgach npx tsc --pretty false — chiqishsiz. node --test 32-recursive/settings.test.ts bizda (Node 24.21.0):
✔ applySettings: faqat berilgan maydon o'zgaradi (2.9562ms)
✔ applySettings: asl sozlama o'zgarmaydi (0.3829ms)
✔ applySettings: undefined eski qiymatni o'chirmaydi (0.3098ms)
✔ toJSON: sozlama JSON'ga aylanadi va qaytadi (0.6491ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 269.6082Uchinchi test — Mapped types darsidagi Partial + spread tuzog'ining himoyasi: oddiy { ...base, ...patch } bo'lganda theme undefined bo'lib qolardi.
XATOLAR.md ga qo'shing: TS2589 (rekursiya chuqur), TS2590 (union juda katta), TS2739 (Partial bir qavatli), "Index signature … is missing" (interface va JSONValue). Keyin kurs/mashqlar papkasidan:
git add 15/32-recursive 15/XATOLAR.md
git commit -m "15/32: settings.ts — DeepPartial va JSONValue"10. Real ishda
- JSON va API turlari:
JSONValue(yoki kutubxonalardagiJsonValue) —localStorage,postMessage, server javoblari uchun. Ko'p loyihalarda u birinchi yoziladigan rekursiv tur. - Sozlamalar va forma qoralamalari:
DeepPartial— test ma'lumotlari ("faqat shu maydonni o'zgartirib, qolgani standart"), sozlamalarni qisman yangilash, ichma-ich formalar. - Daraxtlar: menyu kategoriyalari, izohlar va javoblar, fayl tizimi —
type Category = { name: string; children: Category[] }. Daraxtlar bilan 14-qismda ishlagansiz — endi ularning turini ham yozasiz. - Kutubxona muallifi darajasi: yo'l parametrlarini chiqarish (Express, Next.js turlari), SQL so'rovidan natija turini hisoblash (Drizzle, Kysely kabi kutubxonalar) — chuqur rekursiv turlar. Ularning muallifi
TS2589bilan doimiy kurashadi. Siz esa ularni foydalanuvchi sifatida ishlatasiz. - Intervyu: "
DeepPartialni yozing", "JSONValueturini yozing", "Type instantiation is excessively deep xatosini qanday tuzatasiz?" — yuqori darajadagi savollar.
Xulosa
- Rekursiv tur — ta'rifida o'zi uchraydigan tur; ichma-ich ma'lumot (JSON, sozlamalar, daraxt) uchun.
JSONValueJSON'ga jim buziladiganDate,undefined, funksiyani ushlaydi;NaNni esa ushlamaydi. JSON shakllari —typebilan.DeepPartial—Partialning har qavatga tushadigan versiyasi; primitivda to'xtaydi; massiv vaDateda g'alati — tayyorPartialDeepni oling.- Rekursiv conditional type — satr yoki tuple'ni bo'lakma-bo'lak "yeydi"; har qadamda kichrayishi shart.
- Chegaralar (TypeScript 7.0.2 da o'lchandi): dumli rekursiya ~1000 qadam, dumsiz ~50, union — 100 000 dan kam. Murakkab tur muharrirni sekinlashtiradi — tur ma'lumotni tasvirlasin, hisoblamasin.
Keyingi dars: tsconfig.json chuqur — tur darajasidagi "sehr" tugadi. Endi kompilyatorni sozlaymiz: target, lib, module, moduleResolution va TypeScript 6/7 ning yangi standartlari.
Manbalar
- TypeScript Handbook: "Conditional Types" va "Mapped Types" — typescriptlang.org/docs/handbook/2/types-from-types.html
- TypeScript Release Notes 3.7 (recursive type aliases), 4.1 (recursive conditional types), 4.5 (tail-recursion elimination on conditional types) — typescriptlang.org/docs/handbook/release-notes/
- TypeScript Wiki: "Performance" (murakkab turlar va kompilyator tezligi) — github.com/microsoft/TypeScript/wiki/Performance
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!