Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. DOM turlari qayerdan keladi
- 2.1 lib.dom.d.ts
- 2.2 Elementlar ierarxiyasi
- 3. Element topish: null va to'g'ri tur
- 3.1 Ikki muammo bitta qatorda
- 3.2 Teg nomi bilan — tur o'zi aniqlanadi
- 3.3 Uch yo'l: generic, as, instanceof
- 3.4 ! — eng qisqa va eng xavfli
- 4. Bitta yordamchi — butun sahifa uchun
- 5. Hodisa turlari
- 5.1 Tur hodisa nomidan chiqariladi
- 5.2 Handler alohida funksiya bo'lsa
- 5.3 event.target — eng ko'p uchraydigan tuzoq
- 5.4 currentTarget va this
- 6. Tuzoq: function ichida toraytirish yo'qoladi
- 7. TS nimani ushlamaydi
- 8. Ko'p uchraydigan xatolar
- 8.1 TS18047: '…' is possibly 'null'.
- 8.2 TS2339: Property 'value' does not exist on type 'Element' / 'HTMLElement' / 'EventTarget'.
- 8.3 TS2352 — as bilan qardosh tur
- 8.4 TS2769 — handler turi hodisa nomiga mos emas
- 8.5 ! va as bilan "tuzatish"
- 9. Mashqlar
- 1-mashq (oson): Turni ayting
- 2-mashq (o'rta): Klaviatura yorlig'i
- 3-mashq (qiyin): Amaliy tajriba — buyurtma formasi
- 4-mashq: Vazifalar qadami — DOM modullari .ts ga
- 10. Real ishda
- Xulosa
- Manbalar
TypeScript va DOM: querySelector, null tekshiruvi va hodisa turlari
Qisqacha: Brauzer obyektlarining turlari TypeScript'ning
lib.dom.d.tsfaylidan keladi (tsconfig.jsonda"lib": ["dom"]).document.querySelectorvagetElementByIdelement topmasligi mumkin — natija turi… | null, shuning uchun avval tekshirish shart. UmumiyElementyokiHTMLElementda.valueyo'q: aniq turniinstanceof HTMLInputElementbilan tekshirasiz. Hodisa turi (KeyboardEvent,SubmitEvent)addEventListenerdagi nomdan o'zi chiqariladi,event.targetesaEventTarget | null— u ham toraytiriladi.
Bu darsda
lib.domturlari qayerdan kelishini va DOM elementlar ierarxiyasini (Element→HTMLElement→HTMLInputElement) tushuntira olasiz.querySelectornatijasininulldan va noto'g'ri turdan himoyalaysiz:instanceof, generic,asfarqini bilasiz.- Hodisa turlarini (
KeyboardEvent,PointerEvent,SubmitEvent) vaevent.target/currentTargetni to'g'ri ishlatasiz. vazifalarning to'rt DOM modulini.tsga o'tkazasiz — 219 ta xatoni qanday turlarga bo'lish mumkinligini ko'rasiz.
Oldin bilishingiz kerak: Elementlarni tanlash, Listener opsiyalari, null va undefined bilan xavfsiz ishlash, Custom type guard, Legacy dekoratorlar.
1. Nega bu kerak?
O'tgan ikki darsda klasslarning ichiga kirdik. Endi kursning eng amaliy joyiga qaytamiz — brauzer. 9-qismdan beri «Bahor» sahifalarida document.querySelector ni yuzlab marta yozdingiz. Ko'pi ishladi. Lekin har biri ichida yashirin xavf bor edi.
Sardor menyu sahifasida qidiruv maydonini o'qiydi:
const search = document.querySelector("#search");
console.log(search.value.trim());Bir kuni dizayner HTML'da id="search" ni id="menu-search" ga o'zgartirdi. Sahifa ochildi — qidiruv ishlamaydi, konsolda:
TypeError: Cannot read properties of null (reading 'value')Xato faqat sahifa ochilganda, faqat shu kodga yetib kelganda chiqdi. TypeScript bunday xatoni kod yozilayotganda ko'rsatadi. Lekin buning uchun TypeScript sahifani "ko'ra" olmasligini tushunish kerak. U faqat sizning kodingizni va brauzer haqidagi tur ta'riflarini biladi.
2. DOM turlari qayerdan keladi
2.1 lib.dom.d.ts
Declaration fayllari darsida .d.ts — "kodsiz, faqat turlar" fayl ekanini ko'rdik. TypeScript o'zi bilan katta .d.ts fayllarni olib keladi: lib.es2024.d.ts (JavaScript'ning o'zi: Array, Map, Promise) va lib.dom.d.ts (brauzer: document, HTMLElement, fetch, localStorage). Qaysilari yoqilishini tsconfig.json dagi lib hal qiladi:
"lib": ["es2024", "dom"]lib yozilmasa, target ga mos JavaScript kutubxonasi va dom olinadi. vazifalar da "lib": ["esnext", "dom"]. Node uchun loyihada esa dom kerak emas: unda document yo'q va tsc buni bilishi kerak (tsconfig.json chuqur).
lib.dom.d.ts qo'lda yozilmaydi. U brauzer standartlaridan (WebIDL) avtomatik yasaladi — shuning uchun deyarli har bir brauzer API'sining turi bor. Lekin hammasi emas: «Vazifalar qadami» da Trusted Types'ga tur o'zimiz yozamiz.
2.2 Elementlar ierarxiyasi
DOM turlari — bir-birini kengaytiradigan interfeyslar zanjiri (Kengaytirish):
flowchart TB
A["EventTarget<br/>addEventListener"] --> B["Node<br/>textContent"]
B --> C["Element<br/>closest, classList"]
C --> D["HTMLElement<br/>hidden, dataset, focus"]
D --> E["HTMLInputElement<br/>value, checked"]
D --> F["HTMLButtonElement<br/>disabled"]
D --> G["HTMLFormElement<br/>elements, reset"]Har qavat o'zidan yuqoridagi hamma narsani meros oladi va o'zinikini qo'shadi. .value faqat pastki qavatda — HTMLInputElement da (yana HTMLTextAreaElement, HTMLSelectElement da). Element yoki HTMLElement da .value yo'q. Shuning uchun turi qaysi qavatda ekanini bilish muhim.
3. Element topish: null va to'g'ri tur
3.1 Ikki muammo bitta qatorda
const search = document.querySelector("#search");
console.log(search.value);
const byId = document.getElementById("search");
console.log(byId?.value);Kompilyator:
TS18047: 'search' is possibly 'null'.
TS2339: Property 'value' does not exist on type 'Element'.
TS2339: Property 'value' does not exist on type 'HTMLElement'.- TS18047: "
searchnullbo'lishi mumkin".querySelectorning natija turiElement | null: topilmasa —null. Bu «Nega bu kerak?» bo'limidagiTypeErrorning o'zi, faqat ishga tushmasdan oldin. - TS2339 (
Element):"#search"— bu id.tscid ortida qanday teg turganini bilmaydi:<input>ham,<div>ham bo'lishi mumkin. Shuning uchun eng umumiy turni beradi —Element. Unda.valueyo'q. - TS2339 (
HTMLElement):getElementByIdsal aniqroq —HTMLElement | null.?.nullni hal qildi, lekin.valuebu qavatda ham yo'q.
3.2 Teg nomi bilan — tur o'zi aniqlanadi
const input = document.querySelector("input");
console.log(input?.value);Bu yerda xato yo'q. Hover: const input: HTMLInputElement | null. Selektor faqat teg nomi bo'lsa ("input", "form", "button"), lib.dom dagi HTMLElementTagNameMap jadvalidan aniq tur topiladi (indexed access — HTMLElementTagNameMap["input"]). Murakkab selektorda ("#search", "form input", "[data-id]") — faqat Element. Xuddi shu jadval document.createElement da ham ishlaydi: createElement("input") — HTMLInputElement, createElement("li") — HTMLLIElement. Bu — Generic cheklovlar va keyof darsidagi K extends keyof … naqshi va overload: har teg nomiga o'z natija turi.
3.3 Uch yo'l: generic, as, instanceof
"#search" uchun aniq turni olishning uch yo'li bor. Ular bir-biridan juda farq qiladi:
// 1) Generic — "ishon, bu input"
const a = document.querySelector<HTMLInputElement>("#search");
// 2) as — o'sha va'da, boshqa yozuv
const b = document.getElementById("search") as HTMLInputElement;
// 3) instanceof — ishlash paytida tekshirish
const c = document.getElementById("search");
if (c instanceof HTMLInputElement) {
console.log(c.value);
}
console.log(a?.value, b?.value);- Generic
querySelector<HTMLInputElement>—tscga "natijaHTMLInputElement | nullbo'ladi" deysiz. Hech narsa tekshirilmaydi: HTML'da u aslida<div>bo'lsa,a.valuejimundefinedberadi. as— o'sha va'da, boshqa yozuv (Type assertion). Bu yerda u yana yomonroq:as HTMLInputElementda| nullham yo'q,bning turi —HTMLInputElement.nulltekshiruvi ham "o'chirildi" — eng xavfli variant.instanceof— toraytirish: brauzer elementning haqiqiy klassini tekshiradi.ifichidac—HTMLInputElement, va bu rost.nullham avtomatik chiqib ketadi:null instanceof …—false.
as ba'zan tsc ni ham shubhalantiradi:
const area = document.querySelector("textarea") as HTMLInputElement;Kompilyator:
TS2352: Conversion of type 'HTMLTextAreaElement | null' to type 'HTMLInputElement' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Type 'HTMLTextAreaElement' is missing the following properties from type 'HTMLInputElement': accept, align, alt, capture, and 29 more.Tarjimasi: "HTMLTextAreaElement | null ni HTMLInputElement ga aylantirish xato bo'lishi mumkin — turlar yetarlicha ustma-ust tushmaydi. Ataylab bo'lsa, avval unknown ga aylantiring". textarea va input — qardosh turlar, biri ikkinchisi emas. tsc faqat aniq absurdni ushlaydi. as HTMLElement yoki as unknown as … esa hamma narsani o'tkazib yuboradi.
Qoida: sahifa elementini olishda — instanceof. Generic yoki as — faqat elementni o'zingiz shu kodda yaratgan bo'lsangiz va turini aniq bilsangiz.
3.4 ! — eng qisqa va eng xavfli
const field = document.querySelector<HTMLInputElement>("#qty")!;
console.log(field.valueAsNumber);! (non-null assertion) — "null emas, ishon". Xato yo'q, lekin sahifada #qty bo'lmasa — o'sha TypeError. Kodingizda ! va as qanchalik ko'p bo'lsa, TypeScript shunchalik kam yordam beradi.
Tekshirib ko'ring:
document.querySelector("button")vadocument.querySelector(".pay-button")— hover'da qaysi turlar?
Javob
Birinchisi — HTMLButtonElement | null: teg nomi, tur HTMLElementTagNameMap dan. Ikkinchisi — Element | null: klass selektori ortida istalgan teg bo'lishi mumkin. Ikkalasida ham | null bor — sahifada bunday element umuman bo'lmasligi mumkin.
4. Bitta yordamchi — butun sahifa uchun
Har elementga if (x instanceof …) yozish charchatadi. vazifalar da 26 ta element bor. Yechim — bitta generic funksiya:
function byId<T extends HTMLElement>(
id: string,
type: new () => T,
): T {
const found = document.getElementById(id);
if (!(found instanceof type)) {
throw new TypeError(`#${id} topilmadi yoki ${type.name} emas`);
}
return found;
}
const search = byId("search", HTMLInputElement); // HTMLInputElement
const form = byId("order-form", HTMLFormElement); // HTMLFormElement
console.log(search.value, form.elements.length);Qatorma-qator:
type: new () => T— "newbilan yaratiladigan vaTqaytaradigan narsa", ya'ni klassning o'zi:HTMLInputElement,HTMLFormElement. Klass ham qiymat (ishlash paytidainstanceofuchun), ham tur (Tuchun) — ikkala ishni bitta argument qiladi.instanceof typedan keyinfound—T.tsctoraytirishni generic bilan ham tushunadi.- Topilmasa yoki boshqa teg bo'lsa — darhol aniq xabar bilan
TypeError. HTML va kod ajralib qolsa, sahifa birinchi ochilishda buni aytadi. Jimundefinedemas.
Natija: 26 ta element — 26 ta qisqa qator, birorta as yoki ! yo'q. Bu aynan vazifalar dagi element() funksiyasi.
5. Hodisa turlari
5.1 Tur hodisa nomidan chiqariladi
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
console.log("Menyu yopildi");
}
});
const form = document.querySelector("form");
form?.addEventListener("submit", (event) => {
event.preventDefault();
console.log("Yuborgan tugma:", event.submitter?.id);
});event ga tur yozmadik, lekin hover'da: birinchisida KeyboardEvent, ikkinchisida SubmitEvent. lib.dom da DocumentEventMap, HTMLElementEventMap jadvallari bor: "keydown" → KeyboardEvent, "submit" → SubmitEvent, "input" → Event. addEventListener hodisa nomini shu jadvaldan qidiradi — tur chiqarish ning DOM'dagi eng foydali joyi.
Shuning uchun nom xato bo'lsa, tur ham noto'g'ri bo'ladi:
const list = document.querySelector("ul");
list?.addEventListener("click", (event) => {
console.log(event.key);
});
window.addEventListener("resize", (e) => console.log(e.clientX));Kompilyator:
TS2339: Property 'key' does not exist on type 'PointerEvent'.
TS2339: Property 'clientX' does not exist on type 'UIEvent'."click" — PointerEvent (sichqoncha, barmoq yoki qalam bosishi, Pointer hodisalari). Unda key yo'q — tugma bosilishi uchun "keydown" kerak edi. "resize" — UIEvent, unda sichqoncha koordinatasi yo'q.
Maslahat: Eski darsliklarda
"click"hodisasiMouseEventdeb yoziladi. Hozirgilib.dom(TypeScript 7.0.2)PointerEventdeydi — uMouseEventni kengaytiradi, shuning uchunclientX,buttonikkalasida bor. Brauzer ham shunday: Chrome 154 daclickhodisasiningevent.constructor.namei —PointerEvent(biz tekshirdik).
5.2 Handler alohida funksiya bo'lsa
Handler'ni alohida yozsangiz, turni o'zingiz berasiz — va noto'g'ri bersangiz, tsc ushlaydi:
function onKey(event: KeyboardEvent): void {
console.log(event.key);
}
document.addEventListener("click", onKey);Kompilyator:
TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type '(event: KeyboardEvent) => void' is not assignable to parameter of type 'EventListenerOrEventListenerObject'.
Type '(event: KeyboardEvent) => void' is not assignable to type 'EventListener'.
Types of parameters 'event' and 'evt' are incompatible.
Type 'Event' is missing the following properties from type 'KeyboardEvent': altKey, charCode, code, ctrlKey, and 17 more.Uzun, lekin pastdan o'qing: "Event da KeyboardEvent ning altKey, code… xususiyatlari yo'q". TS2769 — "chaqiruvga birorta ham overload mos kelmadi". addEventListener ning bir nechta imzosi (Function overload) bor: birinchisi "click" uchun PointerEvent kutadi. Oxirgisi — har qanday nom uchun oddiy Event. Xabar oxirgisining xatosini ko'rsatadi. Tuzatish: "keydown" yoki handler'ni (event: PointerEvent) qiling.
5.3 event.target — eng ko'p uchraydigan tuzoq
Event delegation da bitta tinglovchi butun ro'yxatga qo'yiladi va event.target dan bosilgan joy aniqlanadi:
const list = document.querySelector("ul");
list?.addEventListener("click", (event) => {
const button = event.target.closest("button");
console.log(button);
});Kompilyator:
TS18047: 'event.target' is possibly 'null'.
TS2339: Property 'closest' does not exist on type 'EventTarget'.TypeScript aytyapti: "event.target null bo'lishi mumkin" va "EventTarget turida closest yo'q".
event.target ning turi — EventTarget | null. Nega bunchalik umumiy? Hodisani istalgan narsa yuborishi mumkin: element, window, XMLHttpRequest, matn tuguni, hatto o'z EventTarget ingiz. closest esa faqat Element da. tsc sahifani ko'rmaydi va "bu yerda faqat tugmalar bosiladi" deb taxmin qilmaydi.
Tuzatish — instanceof bilan toraytirish:
const list = document.querySelector("ul");
list?.addEventListener("click", (event) => {
if (!(event.target instanceof Element)) {
return;
}
const button = event.target.closest("button");
console.log(button?.dataset.id);
});closest("button") — HTMLButtonElement | null (teg nomi!). vazifalar da bu tekshiruv besh tinglovchida takrorlanadi, shuning uchun u type guard funksiyaga chiqarilgan: function nishonEmi(nishon: EventTarget | null): nishon is Element.
5.4 currentTarget va this
event.currentTarget — tinglovchi qo'yilgan element (<ul>), event.target — bosilgan element (ichidagi tugma). Ishlash paytida currentTarget doim ma'lum. Lekin turi baribir EventTarget | null:
const btn = document.querySelector("button");
btn?.addEventListener("click", (event) => {
const target = event.currentTarget;
target.disabled = true;
});Kompilyator:
TS18047: 'target' is possibly 'null'.
TS2339: Property 'disabled' does not exist on type 'EventTarget'.TypeScript aytyapti: "target null bo'lishi mumkin" va "EventTarget da disabled yo'q".
lib.dom hodisa turini bitta (PointerEvent) qilib yozgan — u qaysi elementga qo'yilganini bilmaydi. Uch yo'l bor:
- Tashqaridagi o'zgaruvchini ishlating —
btnallaqachonHTMLButtonElement(eng sodda,vazifalarshunday qiladi). - Oddiy
functionhandler'dathis—lib.domuni tinglovchi egasining turi bilan beradi:this—HTMLButtonElement. if (event.currentTarget instanceof HTMLButtonElement)— toraytirish.
Tekshirib ko'ring:
form.addEventListener("input", (event) => …)—event.targetning turi qanday? Ichida.valueni o'qish uchun nima qilasiz?
Javob
EventTarget | null — har doimgidek. "input" hodisasi Event turida va forma ichidagi istalgan maydondan kelishi mumkin. .value uchun: if (event.target instanceof HTMLInputElement) { … event.target.value … }. <select> ham bo'lishi mumkin bo'lsa — HTMLSelectElement ni ham tekshiring.
6. Tuzoq: function ichida toraytirish yo'qoladi
vazifalar ko'chishida bitta g'alati xato chiqdi. Uni kichik misolda ko'ramiz:
function setup(): void {
const panel = document.querySelector<HTMLMetaElement>("meta");
if (panel === null) return;
function apply(): void {
panel.content = "#14201a";
}
apply();
}Kompilyator:
TS18047: 'panel' is possibly 'null'.TypeScript aytyapti: "panel null bo'lishi mumkin".
Tekshiruv bor-ku: if (panel === null) return;! Sabab — function e'loni yuqoriga ko'tariladi (hoisting). apply ni tekshiruvdan oldin ham chaqirish mumkin, va o'sha paytda panel null bo'lishi mumkin. tsc buni hisobga oladi va toraytirishni funksiya ichiga olib kirmaydi. Tekshiruvdan keyin yaratilgan arrow funksiyada esa toraytirish saqlanadi:
function setup(): void {
const panel = document.querySelector<HTMLMetaElement>("meta");
if (panel === null) return;
const apply = (): void => {
panel.content = "#14201a";
};
apply();
}
setup();Bu yerda apply faqat tekshiruvdan keyingi qatorda paydo bo'ladi — panel aniq null emas. vazifalar ning tema-tanlash.ts ida aynan shu tuzatish qilindi.
7. TS nimani ushlamaydi
DOM turlari faqat sizning kodingizni tekshiradi. HTML fayl tekshirilmaydi:
index.htmldaidnoto'g'ri yozilsa —tscjim. FaqatbyIdkabi ishlash paytidagi tekshiruv aytadi.querySelector<HTMLInputElement>yokiasbilan bergan va'dangiz yolg'on bo'lsa — jim.innerHTMLga foydalanuvchi matnini yozsangiz — turlar to'g'ri, lekin XSS (XSS). Bu xavfsizlik —tscning emas, ESLint qoidasi va Trusted Types ning ishi.
Maslahat: Ishlash paytidagi tekshiruv (
instanceof) va TypeScript bir-birini to'ldiradi:instanceofhaqiqatni tekshiradi, TypeScript esa siz tekshirishni unutgan har joyni ko'rsatadi.
8. Ko'p uchraydigan xatolar
8.1 TS18047: '…' is possibly 'null'.
querySelector, getElementById, closest, event.target — hammasi null berishi mumkin. Tuzatish: instanceof yoki === null tekshiruvi; yordamchi byId.
8.2 TS2339: Property 'value' does not exist on type 'Element' / 'HTMLElement' / 'EventTarget'.
Tur juda umumiy. Tuzatish: instanceof HTMLInputElement (eng yaxshi), teg nomi bilan selektor yoki generic.
8.3 TS2352 — as bilan qardosh tur
textarea ni HTMLInputElement deb e'lon qildingiz. Tuzatish: to'g'ri tur (HTMLTextAreaElement) va instanceof.
8.4 TS2769 — handler turi hodisa nomiga mos emas
Tuzatish: hodisa nomini tekshiring ("keydown" ↔ KeyboardEvent, "click" ↔ PointerEvent) yoki handler'ni arrow qilib, turni chiqarishga qo'ying.
8.5 ! va as bilan "tuzatish"
Xato yo'qoladi, xavf qoladi. Tuzatish: har !/as ni "nega bunga ishonaman?" savoli bilan tekshiring.
9. Mashqlar
1-mashq (oson): Turni ayting
Hover'da qaysi tur chiqadi (| null siz yozing)?
document.querySelector("select")— .document.querySelector("#menu")— .document.getElementById("menu")— .
Yechim
Teg nomi — aniq tur: HTMLSelectElement. Id selektori querySelector da — Element (id ortida istalgan teg, hatto SVG bo'lishi mumkin). getElementById — HTMLElement: u HTML elementlar uchun yozilgan. Uchalasida ham | null bor.
2-mashq (o'rta): Klaviatura yorlig'i
«Bahor» menyu sahifasida / tugmasi bosilganda qidiruv maydoniga fokus o'tsin, lekin foydalanuvchi allaqachon biror maydonga yozayotgan bo'lsa — yo'q (Klaviatura hodisalari). Qidiruv maydoni — #menu-search. Bitta ham as va ! ishlatmang.
Ishora: "maydonga yozayapti" — event.target HTMLInputElement yoki HTMLTextAreaElement. byId ni «Bitta yordamchi» bo'limidan oling.
Yechim
function byId<T extends HTMLElement>(
id: string,
type: new () => T,
): T {
const found = document.getElementById(id);
if (!(found instanceof type)) {
throw new TypeError(`#${id} topilmadi yoki ${type.name} emas`);
}
return found;
}
const search = byId("menu-search", HTMLInputElement);
document.addEventListener("keydown", (event) => {
const typing =
event.target instanceof HTMLInputElement ||
event.target instanceof HTMLTextAreaElement;
if (event.key !== "/" || typing) {
return;
}
event.preventDefault(); // "/" maydonga yozilib qolmasin
search.focus();
});event — KeyboardEvent (nomdan). event.target ikki instanceof bilan tekshirildi — as kerak bo'lmadi. preventDefault — fokusdan keyin / belgisi qidiruv maydoniga tushib qolmasligi uchun. Bobur (faqat klaviaturadan foydalanadigan mehmon) uchun bunday yorliqlar juda qulay — lekin uni sahifada matn bilan ham ayting ("Qidirish uchun / ni bosing").
3-mashq (qiyin): Amaliy tajriba — buyurtma formasi
kurs/mashqlar/15/41-dom/ da ikki fayl:
order.ts— DOM'siz mantiq:parseOrder(data: FormData): Order.Order—dish(faqatosh,manti,lagmon),quantity(1 dan katta butun son),note(bo'sh joylari kesilgan, bo'lmasa""). Noto'g'ri taom yoki son —RangeError, izoh matn emas (fayl) —TypeError.form.ts— brauzer qismi:#order-formsubmitdaparseOrder(new FormData(form)), natija yoki xato#order-statusga;Escape— formani tozalaydi.asva!yo'q.order.test.ts—node:testbilanparseOrdertestlari.FormDataNode 24 da ham bor — DOM'siz sinash mumkin.
Avval sodda variantni yozib, npx tsc --pretty false ni ishga tushiring:
// 41-dom/order.ts — birinchi, sodda variant
export type Order = {
readonly dish: string;
readonly quantity: number;
readonly note: string;
};
export function parseOrder(data: FormData): Order {
return {
dish: data.get("dish"),
quantity: Number(data.get("quantity")),
note: data.get("note") ?? "",
};
}Yechim
Sodda variantda tsc shunday dedi:
41-dom/order.ts(10,5): error TS2322: Type 'FormDataEntryValue | null' is not assignable to type 'string'.
Type 'null' is not assignable to type 'string'.
41-dom/order.ts(12,5): error TS2322: Type 'FormDataEntryValue' is not assignable to type 'string'.
Type 'File' is not assignable to type 'string'.TypeScript aytyapti: "FormDataEntryValue | null ni string ga berib bo'lmaydi" (10-qator, dish) va "File ni string ga berib bo'lmaydi" (12-qator, note). FormData.get() — FormDataEntryValue | null, ya'ni string | File | null: maydon bo'lmasa null, fayl maydoni bo'lsa File. ?? "" null ni olib tashladi, lekin File qoldi — ikkinchi xato shu.
41-dom/order.ts:
// 41-dom/order.ts — «Bahor» buyurtma formasi: FormData → Order
export type Order = {
readonly dish: string;
readonly quantity: number;
readonly note: string;
};
const DISHES: readonly string[] = ["osh", "manti", "lagmon"];
// FormData.get() — FormDataEntryValue | null: satr, File yoki null
export function parseOrder(data: FormData): Order {
const dish = data.get("dish");
const quantity = Number(data.get("quantity"));
const note = data.get("note") ?? "";
if (typeof dish !== "string" || !DISHES.includes(dish)) {
throw new RangeError(`Noma'lum taom: ${String(dish)}`);
}
if (!Number.isInteger(quantity) || quantity < 1) {
throw new RangeError("Soni — 1 dan katta butun son");
}
if (typeof note !== "string") {
throw new TypeError("Izoh — matn bo'lsin");
}
return { dish, quantity, note: note.trim() };
}
const data = new FormData();
data.append("dish", "manti");
data.append("quantity", "3");
const order = parseOrder(data);
console.log(order.dish, order.quantity); // manti 3typeof dish !== "string" dan keyin dish — string (toraytirish). Number(null) — 0, shuning uchun son maydoni yo'q bo'lsa ham RangeError chiqadi. Oxirgi to'rt qator (va bo'sh qator) — darsda sinab ko'rish uchun, mashq faylida ular yo'q.
41-dom/form.ts:
// 41-dom/form.ts — brauzer qismi: forma va hodisalar
import { parseOrder } from "./order.ts";
function byId<T extends HTMLElement>(
id: string,
type: new () => T,
): T {
const found = document.getElementById(id);
if (!(found instanceof type)) {
throw new TypeError(`#${id} topilmadi yoki ${type.name} emas`);
}
return found;
}
const form = byId("order-form", HTMLFormElement);
const status = byId("order-status", HTMLParagraphElement);
form.addEventListener("submit", (event) => {
event.preventDefault();
try {
const order = parseOrder(new FormData(form));
const { quantity, dish } = order;
status.textContent = `${quantity} ta ${dish} qabul qilindi`;
} catch (error) {
status.textContent =
error instanceof Error ? error.message : "Noma'lum xato";
}
});
form.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
form.reset();
status.textContent = "";
}
});catch (error) — unknown (instanceof Error bilan toraytirildi, batafsil — Async kod va xatolarni tiplash). Xato matni textContent ga yoziladi, innerHTML ga emas.
41-dom/order.test.ts:
import { test } from "node:test";
import assert from "node:assert/strict";
import { parseOrder } from "./order.ts";
function formOf(entries: Record<string, string>): FormData {
const data = new FormData();
for (const [key, value] of Object.entries(entries)) {
data.append(key, value);
}
return data;
}
test("to'g'ri buyurtma", () => {
const entries = { dish: "osh", quantity: "2", note: " achchiq " };
assert.deepEqual(parseOrder(formOf(entries)), {
dish: "osh",
quantity: 2,
note: "achchiq",
});
});
test("noma'lum taom va noto'g'ri son — RangeError", () => {
const pizza = formOf({ dish: "pizza" });
assert.throws(() => parseOrder(pizza), RangeError);
const zero = formOf({ dish: "manti", quantity: "0" });
assert.throws(() => parseOrder(zero), RangeError);
});
test("izoh fayl bo'lsa — TypeError", () => {
const data = formOf({ dish: "lagmon", quantity: "1" });
data.append("note", new Blob(["x"]), "izoh.txt");
assert.throws(() => parseOrder(data), TypeError);
});npx tsc --pretty false — chiqishsiz. node --test 41-dom/order.test.ts — bizda (Node 24.21.0):
✔ to'g'ri buyurtma (59.4469ms)
✔ noma'lum taom va noto'g'ri son — RangeError (1.0289ms)
✔ izoh fayl bo'lsa — TypeError (1.2642ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 331.8413XATOLAR.md ga bu darsning yangi xabarlarini qo'shing: TS18047 (DOM), TS2352, TS2769. Keyin:
git add 15/41-dom 15/XATOLAR.md
git commit -m "15/41: buyurtma formasi — FormData va testlar"4-mashq: Vazifalar qadami — DOM modullari .ts ga
vazifalar ning eng katta to'rt moduli DOM bilan ishlaydi: render.js, asosiy.js, tema-tanlash.js, fayl.js. Strict bayroqlar qadamidan keyin ular hali JavaScript edi. Endi ular .ts ga o'tadi.
- Branch:
refactor/dom-turlari - Commit:
refactor: render, asosiy, tema-tanlash, fayl — DOM turlari bilan .ts, tanasi:element() — instanceof; event.target toraytiriladi; trusted-types.d.ts
- To'rt faylni
git mvbilan.tsga o'tkazing vanpm run tipni ishga tushiring. Xatolarni kodlari bo'yicha sanang. render.tsdagi 26 tadocument.getElementById(…)nielement(id, Klass)yordamchisiga almashtiring.asosiy.tsdagi tinglovchilardaevent.targetninishonEmitype guard'i bilan toraytiring.tema-tanlash.tsdagifunction qolla()ni tekshiruvdan keyingi arrow funksiyaga aylantiring.- Trusted Types uchun
trusted-types.d.tsyarating (pastda).
Tekshiruv: npm run build, npm test, npm run tip, npm run lint, npm run format:check — hammasi exit 0.
Yechim
1-qadam: xatolar soni. To'rt fayl nomi o'zgartirilganda (to'liq strict oilasi bilan) tsc 219 xato berdi:
- TS18047 (null)106 xato
- TS7006 (any)50 xato
- TS2339 (xususiyat)37 xato
- TS25516 xato
- TS70054 xato
- TS180464 xato
- boshqa 6 kod12 xato
Manba: O'lchandi: tsc 7.0.2 --noEmit, vazifalar #34 holati + 4 fayl nomi .ts, 2026-10-06
Deyarli yarmi — TS18047 ("… is possibly 'null'"): har getElementById natijasi tekshirilmagan edi. TS7006 — parametr turi yo'q (function render(vazifalar)). TS2339 — HTMLElement da .value yo'q va event.target.closest. Yaxshi xabar: xatolar ko'p, lekin turlari kam — bir xil tuzatish ko'p joyga qo'llanadi. render.ts tiplangach, asosiy.ts da 59 tasi qoldi (u render dan elementlarni import qiladi).
2-qadam: element(). Oldin (render.js):
export const maydon = document.getElementById("yangi-matn");Keyin (render.ts) — «Bitta yordamchi» bo'limidagi byId ning o'zi:
// getElementById — HTMLElement | null: TS sahifani ko'rmaydi. Element
// yo'q yoki boshqa turda bo'lsa — darhol aniq xato (index.html va kod
// ajralib qolgani birinchi ochilishda ko'rinadi). instanceof — ish
// vaqtidagi tekshiruv, "as HTMLInputElement" esa faqat va'da bo'lardi
function element<T extends HTMLElement>(
id: string,
tur: new () => T,
): T {
const topildi = document.getElementById(id);
if (!(topildi instanceof tur)) {
throw new TypeError(`#${id} topilmadi yoki ${tur.name} emas`);
}
return topildi;
}
// Sahifa elementlari
export const forma = element("yangi-forma", HTMLFormElement);
export const maydon = element("yangi-matn", HTMLInputElement);
export const saralashTanlov = element("saralash", HTMLSelectElement);
export const royxat = element("royxat", HTMLUListElement);
const ochirishDialog = element("ochirish-dialog", HTMLDialogElement);(26 qatordan beshtasi.) Faqat tahrir paytida paydo bo'ladigan maydon uchun boshqa funksiya — u null qaytaradi, chunki "yo'q" — normal holat: tahrirMaydoni(): HTMLInputElement | null.
3-qadam: hodisalar. asosiy.ts da:
// Hodisa nishoni — EventTarget | null: matn tuguni, oyna yoki null
// bo'lishi mumkin. closest() faqat Element'da — avval toraytiramiz
function nishonEmi(nishon: EventTarget | null): nishon is Element {
return nishon instanceof Element;
}
royxat.addEventListener("click", (event) => {
if (!nishonEmi(event.target)) {
return;
}
const nishon = event.target.closest<HTMLElement>("[data-amal]");
if (nishon === null || !royxat.contains(nishon)) {
return;
}
// …
});closest<HTMLElement> — generic: data-amal atributi faqat HTML elementlarda turadi, .dataset uchun HTMLElement kerak. Bu — "o'zimiz yaratgan element" holati: render.ts bu tugmalarni o'zi chizadi. Fayl tanlash va sudrab tashlashda ham null bor:
// FileList | null → File | undefined
const fayl = importFayl.files?.[0];
const faylmi = (event: DragEvent) =>
event.dataTransfer?.types.includes("Files") ?? false;
for (const tur of ["dragover", "drop"] as const) {
addEventListener(tur, (event) => { /* event — DragEvent */ });
}as const — massiv readonly ["dragover", "drop"] bo'ladi (Massiv va tuple), shuning uchun tur — "dragover" | "drop", oddiy string emas. Natijada addEventListener hodisa turini topadi — DragEvent. as const siz event oddiy Event bo'lib qolardi.
Yana: boshMatnlar: Readonly<Record<Filtr, string>> — har filtrga bo'sh ro'yxat matni (Index signature va Record): yangi filtr qo'shilsa, matni unutilmaydi. aloqaMatnlari — satisfies Record<TarmoqHolati, string> (satisfies), boshqaTabdanYangila — switch (xabar.tur) + never (to'liqlik tekshiruvi qadamidagi va'da), filtrniTanla — FILTRLAR.find((f) => f === qiymat): oq ro'yxat tekshiruvi va Filtr turiga toraytirish birdaniga.
4-qadam: tema-tanlash.ts. «function ichida toraytirish yo'qoladi» bo'limidagi holat: function qolla() ichida panel va guruh ga haqiqiy TS18047 'panel' is possibly 'null' (×2) va TS2339 'elements' chiqdi. Tuzatish — const qolla = (): void => { … } tekshiruvdan keyin.
5-qadam: trusted-types.d.ts. vazifalar CSP'da Trusted Types yoqilgan (13-qism). Service Worker manzili siyosatdan o'tadi: navigator.serviceWorker.register(siyosat.createScriptURL("sw.js")). Lekin lib.dom da Trusted Types turlari yo'q (TypeScript 7.0.2 ning lib.dom.d.ts ida TrustedScriptURL — 0 marta). Kodsiz tur fayli (Declaration fayllari):
interface TrustedScriptURL {
// Brend: oddiy satr bilan almashib ketmasin — faqat siyosatdan
readonly __trustedScriptURL: unique symbol;
}
interface TrustedTypePolicy {
createScriptURL(url: string): TrustedScriptURL;
}
// Trusted Types bo'lmagan brauzerda (Firefox) — undefined.
// eslint-disable-next-line no-var
declare var trustedTypes: TrustedTypePolicyFactory | undefined;
// Declaration merging: lib.dom dagi interfeysga yangi overload
interface ServiceWorkerContainer {
register(
manzil: string | URL | TrustedScriptURL,
sozlama?: RegistrationOptions,
): Promise<ServiceWorkerRegistration>;
}(TrustedTypePolicyFactory ham shu faylda, qisqartirildi.) ServiceWorkerContainer — declaration merging: lib.dom dagi interfeysga yangi register imzosi qo'shildi. Birinchi urinishda imzo faqat TrustedScriptURL bilan yozilgan edi va TS2769: No overload matches this call chiqdi: kodda "sw.js" | TrustedScriptURL union bor, u bitta overload'ga tushmaydi. string | URL | TrustedScriptURL bilan — toza. __trustedScriptURL: unique symbol — brend tur: oddiy satrni TrustedScriptURL deb berib bo'lmaydi.
Xulqdagi kichik farqlar (faqat imkonsiz holatlarda): vazifa topilmasa katakniAlmashtir, qatorniOchir, ochirishniSorab endi hech narsa qilmaydi (oldin TypeError); temaniUla elementlar bo'lmasa qaytadi.
Natija: npm run tip — 0 xato. npm test — 137/137. Brauzer tekshiruvi (dist/, Chrome 154) — 159/159. Diff: 9 fayl, +434 −222.
git switch -c refactor/dom-turlari
git mv assets/js/render.js assets/js/render.ts
git mv assets/js/asosiy.js assets/js/asosiy.ts
git mv assets/js/tema-tanlash.js assets/js/tema-tanlash.ts
git mv assets/js/fayl.js assets/js/fayl.ts
# turlar, element(), nishonEmi, trusted-types.d.ts
npm run build && npm test && npm run tip
npm run lint && npm run format:check
git add .
git commit \
-m "refactor: render, asosiy, tema-tanlash, fayl — DOM turlari bilan .ts" \
-m "element() — instanceof; event.target toraytiriladi; trusted-types.d.ts"
git push -u origin refactor/dom-turlari
gh pr create --fill
gh pr merge --mergeCommit sarlavhasi — 70 belgi (≤ 72). Bir narsa hali vaqtinchalik: serverRoyxatiniOl da server javobi as ServerRoyxati bilan "va'da" qilindi. Bu — keyingi darsning mavzusi.
10. Real ishda
- Har frontend loyiha — React, Vue yoki oddiy DOM —
lib.domturlari ustida ishlaydi. React'da (17-qism)event: React.ChangeEvent<HTMLInputElement>kabi yozuvlarni ko'rasiz — g'oya o'sha: hodisa va element turi. !vaassoni — kod sharhida (code review) e'tibor beriladigan belgi. Ko'p jamoalar ESLint'da@typescript-eslint/no-non-null-assertionqoidasini yoqadi.- Brauzer API turlari yangilanib turadi:
lib.domhar TypeScript versiyasida o'zgaradi (click→PointerEventkabi). Yangi API turi hali yo'q bo'lsa —vazifalardagitrusted-types.d.tskabi o'z.d.tsingiz. - Intervyu: "
querySelectornegaElement | nullqaytaradi?", "event.targetvacurrentTargetfarqi?", "asvainstanceoffarqi?".
Xulosa
- DOM turlari —
lib.dom.d.tsdan ("lib": ["dom"]). Ierarxiya:EventTarget→Node→Element→HTMLElement→HTMLInputElement. querySelector/getElementById—… | null. Teg nomi bilan selektor aniq tur beradi, id/klass —Element.- Aniq tur uchun —
instanceof(rost tekshiruv). Generic,as,!— faqat va'da; HTML o'zgarsa jim buziladi. - Hodisa turi nomdan chiqariladi (
"keydown"→KeyboardEvent,"click"→PointerEvent).event.targetvacurrentTarget—EventTarget | null, ular ham toraytiriladi. functione'loni ichida toraytirish yo'qoladi — tekshiruvdan keyingi arrow funksiya ishlating.
Keyingi dars: API ma'lumotlarini tiplash — serverdan kelgan JSON aslida unknown; tur yozish uni tekshirmaydi, as ServerRoyxati o'rniga haqiqiy tekshiruv yozamiz.
Manbalar
- TypeScript Handbook: "DOM Manipulation" — typescriptlang.org/docs/handbook/dom-manipulation.html
- TypeScript-DOM-lib-generator (lib.dom.d.ts qanday yasaladi) — github.com/microsoft/TypeScript-DOM-lib-generator
- MDN: "Event.target", "Event.currentTarget", "PointerEvent", "FormData.get()" — developer.mozilla.org
- W3C: "Trusted Types" — w3c.github.io/trusted-types/dist/spec/
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!