Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Debounce React'da
- 2.1 Tuzoq: renderdagi debounce
- 2.2 To'g'risi: bitta nusxa va tozalash
- 2.3 Qiymatni kechiktirish: useDebouncedValue
- 2.4 Debounce yoki useDeferredValue?
- 2.5 vazifalar dagi debounce: kechiktir
- 3. Throttle
- 3.1 Oraliqda bittasi
- 3.2 Scroll: requestAnimationFrame va qadam
- 4. Web Worker React'da
- 4.1 Muammo: bitta og'ir funksiya
- 4.2 Vite'da Worker ulash
- 4.3 Trusted Types: Worker manzili ham "xavfli joy"
- 4.4 Ishchi fayli
- 4.5 Promise o'rami
- 4.6 postMessage nusxalaydi
- 4.7 Comlink
- 5. Vazifalar qadami: statistika — Web Worker'da (holat 24)
- 5.1 O'zgarishlar
- 5.2 O'lchov
- 5.3 Tekshiruvlar va commit
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): qaysi vosita?
- 2-mashq (o'rta): qoralama saqlash
- 3-mashq (qiyin): useThrottle hook'i
- 4-mashq: vazifalar qadami
- 8. Real ishda
- Xulosa
- Manbalar
Og'ir ishni UI'dan ajratish: debounce, throttle va Web Worker
Qisqacha: Uch xil "ortiqcha ish" — uch xil vosita. Debounce — tez-tez keladigan chaqiruvlardan faqat oxirgisini, to'xtagandan keyin bajaradi (qidiruvdan server so'rovi, qoralamani saqlash). Throttle — oraliqda ko'pi bilan bittasini (scroll, sudrash, tugmani qayta-qayta bosish). Web Worker — og'ir hisobni alohida oqimga chiqaradi, UI esa javob berishda davom etadi. React'dagi asosiy tuzoq: debounce funksiyasini render ichida yasash — har renderda yangi taymer, debounce ishlamaydi; to'g'risi —
useMemo/useRefbilan bitta nusxa va unmount'dacancel(). Kanonvazifalarda statistika hisobi Worker'ga ko'chdi: 10 000 vazifada panel ochilishidagi 53–54 ms lik uzun vazifa — yo'qoldi.
Bu darsda
- Debounce va throttle'ni React komponentida to'g'ri (bitta nusxa, tozalash bilan) yozasiz.
- "Render ichidagi debounce" tuzog'ini konsolda ko'rasiz.
- Debounce,
useDeferredValue, throttle va Worker'dan qaysi biri qachon kerakligini tanlaysiz. - Vite'da Web Worker ulash usullarini (
new URL,?worker,?worker&url) va Trusted Types talabini bilasiz. vazifalarstatistikasini Worker'ga ko'chirib, uzun vazifalar bilan o'lchaysiz.
Oldin bilishingiz kerak: Debounce va throttle, Web Workers, Custom hooklar II: tayyor retseptlar, useDeferredValue, use(): promise va context'ni o'qish.
1. Nega bu kerak?
18-qismning performans bo'limida React renderini tezlashtirdik: transition, memo, virtualizatsiya. Lekin har sekinlik render emas. Performance'ni o'lchash darsidagi Performance panelini eslang — asosiy oqimda React'dan tashqari ish ham bor:
- Qidiruv maydoni har harfda serverga so'rov yuborsa — "osh" uchun uchta so'rov, ikkitasi keraksiz. Bu render muammosi emas, chaqiruvlar soni muammosi.
- Scroll hodisasi soniyasiga 60+ marta keladi, har birida og'ir hisob — yana chaqiruvlar soni.
vazifalardagi batafsil statistika 10 000 vazifada bitta funksiya chaqiruvi — lekin u asosiy oqimni 50 ms dan ortiq band qiladi. Uni na transition (komponent ichida to'xtay olmaydi, Concurrent rendering), namemo(birinchi hisobni tejamaydi) qisqartiradi.
Uchala muammoni 13-qismda vanilla JS'da ko'rgansiz (Debounce va throttle, Web Workers). React'da ularning o'z tuzoqlari bor — renderlar orasida nusxani saqlash, tozalash va natijani state'ga qaytarish.
Hayotiy o'xshatish: «Bahor» ofitsianti. Mehmon buyurtmani o'zgartirib turibdi — ofitsiant har so'zda oshxonaga yugurmaydi, mehmon gapirib bo'lishini kutadi (debounce). Telefon tinimsiz jiringlasa — har daqiqada bir marta ko'taradi (throttle). Osh damlash esa — alohida oshpazga, ofitsiant zalda qoladi (Worker).
2. Debounce React'da
2.1 Tuzoq: renderdagi debounce
Debounce va throttle darsidagi debounce funksiyasi — closure ichida bitta taymer. Uni komponentga to'g'ridan-to'g'ri qo'yamiz. Mehmon qidiruvga "o", "os", "osh" yozadi (har harf orasida tekshiruvchi 60 ms kutadi — tez yozuvchi odamdek):
import { useState } from "react";
function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
function search(text) {
console.log("so'rov:", text);
}
function App() {
const [text, setText] = useState("");
// ❌ har renderda yangi debounce
const debouncedSearch = debounce(search, 400);
function handleChange(e) {
setText(e.target.value);
debouncedSearch(e.target.value);
}
return (
<input
placeholder="Qidiruv"
value={text}
onChange={handleChange}
/>
);
}Konsolda:
so'rov: o
so'rov: os
so'rov: oshUchta so'rov — debounce ishlamadi. Sababi: setText App ni qayta chizadi, render esa debounce(search, 400) ni qaytadan chaqiradi — yangi closure, yangi timer o'zgaruvchisi. Keyingi harf yangi debounce'ga tushadi, eskisining taymerini hech kim bekor qilmaydi. Har nusxa o'z yagona chaqiruvini 400 ms dan keyin bajaradi. Bu Pure komponentlar va Closure qoidalarining birgalikdagi oqibati: render — funksiyani qayta chaqirish, ichidagi hamma narsa yangi.
2.2 To'g'risi: bitta nusxa va tozalash
Debounce funksiyasi renderlar orasida bitta bo'lishi kerak. useMemo(…, []) buning eng qisqa yo'li (memo, useMemo, useCallback):
import { useEffect, useMemo, useState } from "react";
function debounce(fn, ms) {
let timer;
const debounced = (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
debounced.cancel = () => clearTimeout(timer);
return debounced;
}
function search(text) {
console.log("so'rov:", text);
}
function App() {
const [text, setText] = useState("");
const debouncedSearch = useMemo(() => debounce(search, 400), []);
useEffect(() => () => debouncedSearch.cancel(), [debouncedSearch]);
function handleChange(e) {
setText(e.target.value);
debouncedSearch(e.target.value);
}
return (
<input
placeholder="Qidiruv"
value={text}
onChange={handleChange}
/>
);
}Konsolda:
so'rov: oshBitta so'rov — oxirgi matn bilan. Ikki o'zgarish:
useMemo(() => debounce(search, 400), [])— debounce bir marta yasaladi, keyingi renderlarda o'sha funksiya va o'shatimer.useEffecttozalovchisicancel()ni chaqiradi (useEffect III: cleanup). Komponent yopilsa (boshqa sahifaga o'tildi), kutayotgan taymer endi yo'q komponent uchun so'rov yubormaydi.
Uchinchi nozik joy — eskirgan closure. Bu yerda search komponentdan tashqarida va hamma ma'lumotni argumentda oladi (debouncedSearch(e.target.value)). Agar debounce qilinadigan funksiya state yoki props'ni o'qisa, u birinchi renderdagi qiymatlarni ko'radi (massiv bo'sh). Yechim — qiymatni argument bilan bering yoki "latest ref" naqshini ishlating (useDeferredValue darsidagi vazifalar qator hodisalari kabi).
Tekshirib ko'ring:
useMemoo'rnigauseCallback(debounce(search, 400), [])yozilsa, debounce ishlaydimi?
Javob
Ishlaydi (qaytgan funksiya o'sha bo'ladi), lekin isrof bilan: debounce(search, 400) argument sifatida har renderda chaqiriladi va yangi closure yasaladi — useCallback faqat uni tashlab yuborib, birinchisini qaytaradi. useMemo(() => debounce(…), []) esa funksiyani faqat bir marta chaqiradi. Ikkalasi ham to'g'ri natija beradi, useMemo — aniqroq niyat.
2.3 Qiymatni kechiktirish: useDebouncedValue
Ba'zan funksiya emas, qiymat kerak: "matn 400 ms o'zgarmay tursa — shu qiymat bilan ishlayman". Custom hooklar II darsida bunday hook yozgansiz:
function useDebouncedValue(value, ms) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), ms);
return () => clearTimeout(timer);
}, [value, ms]);
return debounced;
}Effekt har value o'zgarishida eski taymerni tozalaydi va yangisini qo'yadi — debounce'ning o'zi, faqat React tilida. Narxi — qo'shimcha render (setDebounced). Natija qiymatiga bog'liq so'rovni esa effektda yuborasiz (race condition'ga ehtiyot bo'lib, useEffect III).
Tayyor kutubxona — use-debounce: useDebounce(value, ms) (qiymat) va useDebouncedCallback(fn, ms) (funksiya, cancel, flush, maxWait bilan). U renderlar orasida nusxani o'zi saqlaydi va eng so'nggi fn ni chaqiradi — eskirgan closure muammosi yo'q:
import { useDebouncedCallback } from "use-debounce";
const debouncedSearch = useDebouncedCallback((text) => {
fetchDishes(text);
}, 400);Natija oynasi faqat react va react-dom ni yuklaydi — bu blokni o'z loyihangizda sinang.
2.4 Debounce yoki useDeferredValue?
useDeferredValue darsida bu savolni ko'rgan edik. Qisqasi:
| Debounce | useDeferredValue |
|
|---|---|---|
| Nima kamayadi | chaqiruvlar soni (so'rov, yozish) | hech narsa — og'ir render to'xtatib bo'ladigan bo'ladi |
| Kechikish | doim ms (masalan 400 ms) |
tez kompyuterda deyarli yo'q |
| Qachon | tarmoq, localStorage, analitika |
xotiradagi ro'yxatni filtrlash, grafik |
vazifalar da ikkalasi ham bor va har biri o'z joyida. Qidiruv xotiradagi ro'yxatda — debounce yo'q, useDeferredValue bor. App.tsx dagi izoh buni aytadi: "Har harfda darhol, kechiktirishsiz (debounce yo'q): qidiruv xotiradagi ro'yxatda, tarmoq va localStorage'ga tegmaydi". Qoralama esa localStorage ga yoziladi — debounce bilan.
2.5 vazifalar dagi debounce: kechiktir
taymer.ts (13-qismdan) — debounce'ning to'liqroq ko'rinishi, ikki qo'shimcha metod bilan:
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;
…
return Object.assign(kechiktirilgan, { hoziroq, bekor });
}App.tsx da u modul darajasida yasalgan — komponentdan tashqarida, demak renderlar uni qayta yasamaydi (useMemo ham kerak emas):
// Qoralama har harfda emas, yozish to'xtagach bir marta saqlanadi:
// localStorage sinxron — har harfda yozish kiritishni sekinlatadi
const QORALAMA_KUTISH_MS = 400;
const qoralamaniKechiktir = kechiktir(
qoralamaniSaqla,
QORALAMA_KUTISH_MS,
);Va ikki muhim chaqiruv: useOynaHodisasi("pagehide", () => qoralamaniKechiktir.hoziroq()) — tab yopilayotganda kutayotgan qoralama darhol saqlanadi (aks holda oxirgi 400 ms yo'qolardi). Forma yuborilganda esa qoralamaniKechiktir.bekor() — eski qoralama keyin yozilib, tozalangan maydonni qayta to'ldirmasin. Bu flush va cancel — use-debounce'dagi o'sha ikki metod.
3. Throttle
3.1 Oraliqda bittasi
Throttle — chaqiruvlar qanchalik tez kelmasin, ms oraliqda ko'pi bilan bittasi bajariladi. Debounce'dan farqi: debounce kutadi (jim bo'lguncha), throttle esa darhol bajaradi va keyin bir muddat "eshitmaydi". «Saqlash» tugmasini sabrsiz mehmon ketma-ket bosmoqda:
import { useRef } from "react";
function App() {
const last = useRef(-Infinity);
function save() {
const now = performance.now();
if (now - last.current < 1000) {
console.log("o'tkazildi");
return;
}
last.current = now;
console.log("saqlandi");
}
return <button onClick={save}>Saqlash</button>;
}Konsolda:
saqlandi
o'tkazildi
o'tkazildi
saqlandiOxirgi bajarilish vaqti — useRef da: u renderlar orasida saqlanadi va o'zgarganda render chaqirmaydi (useRef I). Boshlang'ich -Infinity — birinchi bosish doim o'tadi. Bu leading (oldingi chetdagi) throttle: birinchisi darhol, keyingilar tashlanadi. Trailing variant oraliq oxirida oxirgisini ham bajaradi — scroll uchun muhim (oxirgi holat yo'qolmasin).
Tugma uchun throttle'dan ham yaxshiroq yo'l bor — disabled={isPending} (useTransition darsidagi async transition): so'rov tugaguncha tugma bosilmaydi. Throttle — vaqt bo'yicha, isPending — haqiqiy holat bo'yicha.
3.2 Scroll: requestAnimationFrame va qadam
Scroll, resize, pointermove — brauzer ularni har kadrdan ko'proq yuborishi mumkin. Ekran baribir soniyasiga ~60 marta yangilanadi, shuning uchun eng tabiiy throttle — kadrga bittasi: requestAnimationFrame (requestAnimationFrame). Virtualizatsiya darsida esa uchinchi yo'lni ko'rdik: useSyncExternalStore snapshot'ini 50 px lik qadamga yaxlitlash. Hodisa har safar o'qiladi, lekin qadam o'zgarmasa — render yo'q. Bu ham throttle'ning bir ko'rinishi, faqat vaqt emas, masofa bo'yicha.
flowchart TD
A{"Muammo nima?"} -->|"Chaqiruv ko'p: so'rov, yozish"| B["Debounce"]
A -->|"Hodisa oqimi: scroll, sudrash"| C["Throttle yoki rAF"]
A -->|"Og'ir render, xotiradagi ma'lumot"| D["useDeferredValue"]
A -->|"Bitta og'ir hisob, 50 ms+"| E["Web Worker"]Diagrammadagi to'rtta shox — to'rt xil kasallik. Noto'g'ri dori kasalni davolamaydi: debounce og'ir hisobni tezlashtirmaydi, Worker esa so'rovlar sonini kamaytirmaydi.
4. Web Worker React'da
4.1 Muammo: bitta og'ir funksiya
vazifalar dagi batafsil statistika (Suspense va use() darslarida qurilgan) — batafsilHisobla(vazifalar): teglar, ko'p uchragan so'zlar, foizlar. Node 24 da 10 000 vazifa uchun 26 ms (mediana, natijalar/24-keyin.txt). Brauzerda, sahifa bilan birga — kanon o'lchovida panel ochilishida 53–54 ms lik uzun vazifa (Long Task — 50 ms dan uzun vazifa, Performance API).
50 ms — chegara. Shundan uzun vazifa paytida kelgan bosish kutadi va INP'ga qo'shiladi. Bu hisob React'ning render'idan tashqarida (setTimeout da, Promise ichida) — transition uni to'xtata olmaydi.
4.2 Vite'da Worker ulash
Web Workers darsida new Worker("worker.js") ni ko'rgansiz. Vite loyihasida fayl build paytida hash'li nom oladi va TypeScript'dan o'giriladi — shuning uchun manzilni Vite'ning o'zi berishi kerak (Vite IV: maxsus importlar). Uch yo'l:
| Yo'l | Kod | Qachon |
|---|---|---|
new URL |
new Worker(new URL("./ishchi.ts", import.meta.url), { type: "module" }) |
standart, bundler'lar tushunadi |
?worker |
import Ishchi from "./ishchi.ts?worker"; new Ishchi() |
eng qisqa |
?worker&url |
import manzil from "./ishchi.ts?worker&url"; new Worker(manzil) |
manzilni o'zingiz tekshirishingiz kerak bo'lsa |
vazifalar uchinchisini tanladi. Sababi — keyingi bo'limda.
4.3 Trusted Types: Worker manzili ham "xavfli joy"
vazifalar ning CSP'sida require-trusted-types-for 'script' bor (Brauzer xavfsizlik modeli). Bu siyosat ostida skript manzilini qabul qiladigan har joy — script.src, serviceWorker.register va new Worker — oddiy satrni rad etadi. Kanondagi e2e-18 buni tasdiqladi: new Worker(oddiySatr) CSP ostida TypeError; boshqa nomli siyosat va blob: ishchi ham — TypeError.
?worker varianti manzilni Vite'ning ichida, bizning siyosatimizdan o'tkazmasdan ishlatardi. ?worker&url esa faqat satrni beradi — uni o'zimiz siyosatdan o'tkazamiz. xavfsiz-manzil.ts (holat 24, 13-qismdagi siyosat alohida modulga ko'chdi):
import ishchiManzili from "./statistika-ishchi.ts?worker&url";
export const SW_MANZILI = "sw.js";
// Statistika ishchisi (18/#24): Vite build'da hash'li fayl
// (assets/statistika-ishchi-<hash>.js) — manzilini Vite o'zi beradi
export const ISHCHI_MANZILI: string = ishchiManzili;
const RUXSAT: ReadonlySet<string> = new Set([
SW_MANZILI,
ISHCHI_MANZILI,
]);
const siyosat = globalThis.trustedTypes?.createPolicy("vazifalar", {
createScriptURL(url) {
if (!RUXSAT.has(url)) {
throw new TypeError(`Ruxsat etilmagan skript manzili: ${url}`);
}
return url;
},
});
/** Skript manzili — siyosatdan o'tgan (Trusted Types bo'lsa) */
export function skriptManzili(url: string): string | TrustedScriptURL {
return siyosat?.createScriptURL(url) ?? url;
}Oq ro'yxat — ikki aniq manzil (naqsh emas). Siyosat CSP'da trusted-types vazifalar nomi bilan va bir marta yaratilishi mumkin — shuning uchun SW ham, Worker ham shu bitta moduldan foydalanadi. Bu Frontend xavfsizligi darsida batafsil.
4.4 Ishchi fayli
// statistika-ishchi.ts — Web Worker (18/#24): batafsil
// statistika hisobi alohida oqimda. … Ishchida DOM, React,
// localStorage yo'q — faqat sof hisob (batafsil-statistika.ts,
// node:test bilan sinalgan)
import { batafsilHisobla } from "./batafsil-statistika.ts";
…
ishchi.onmessage = (event) => {
const { id, vazifalar } = event.data;
ishchi.postMessage({ id, natija: batafsilHisobla(vazifalar) });
};Izoh qisqartirildi (…). Ishchi — oddiy modul: batafsilHisobla o'sha sof funksiya, faqat endi boshqa oqimda chaqiriladi. Hisobni avval sof funksiyaga ajratgan edik (React'siz, DOM'siz) — shuning uchun ko'chirish 31 qatorli fayl bilan bo'ldi. Javobda id qaytariladi — nega, keyingi bo'limda.
4.5 Promise o'rami
React tomoni Worker'ni bilmasligi kerak: StatistikaPaneli use(statistikaVadasi(vazifalar)) qiladi va Promise kutadi (use()). Demak, Worker'ning postMessage/onmessage juftini Promise qaytaradigan funksiyaga o'rash kerak. Muammo: bir vaqtda bir nechta so'rov bo'lishi mumkin (mehmon katak bosdi — yangi ro'yxat, eski hisob hali kelmagan). Javob qaysi so'rovniki? Yechim — har so'rovga id.
Natija oynasida Worker yo'q (sandbox CSP'si ruxsat bermaydi, Web Workers darsida aytilgan). Shuning uchun naqshni soxta ishchi bilan ko'ramiz: u postMessage ni oladi va javobni setTimeout bilan, teskari tartibda qaytaradi — haqiqiy Worker'dagi kabi asinxron:
// Soxta ishchi: haqiqiy Worker o'rnida (natija oynasida Worker yo'q)
const worker = {
onmessage: null,
postMessage({ id, items }) {
const delay = id === 1 ? 200 : 100; // 2-so'rov oldin qaytadi
setTimeout(() => {
worker.onmessage({ data: { id, result: items.length } });
}, delay);
},
};
let lastId = 0;
const pending = new Map();
worker.onmessage = (event) => {
const { id, result } = event.data;
pending.get(id)?.(result);
pending.delete(id);
};
function calculate(items) {
return new Promise((resolve) => {
const id = ++lastId;
pending.set(id, resolve);
worker.postMessage({ id, items });
});
}
function App() {
async function run() {
const a = calculate(["osh", "manti", "somsa"]);
const b = calculate(["choy"]);
console.log("javoblar:", await a, await b);
}
return <button onClick={run}>Ikki hisob</button>;
}Konsolda:
javoblar: 3 1| Vaqt | Nima bo'ldi |
|---|---|
| 0 ms | ikki so'rov ketdi: id 1 (3 taom) va id 2 (1 taom) |
| 100 ms | id 2 javobi keldi → pending dan id 2 ning resolve i → b = 1 |
| 200 ms | id 1 javobi keldi → a = 3; ikkala await tugadi, konsolga yoziladi |
Ikkinchi so'rov birinchi qaytdi (100 ms), lekin a baribir 3, b — 1: har javob o'z id'si bo'yicha o'z Promise'ini topdi. Map — id → resolve. Id bo'lmasa, birinchi kelgan javob birinchi kutayotganga tushardi va statistika boshqa ro'yxatniki bo'lardi.
Kanondagi statistika-vadasi.ts — xuddi shu naqsh, yana uch narsa bilan:
- Rad etish —
kutilmoqdaMap'ida{ hal, rad }; ishchi yiqilsa (errorhodisasi) — hamma kutayotganlar rad etiladi, ishchiterminate()qilinadi va keyingi so'rov yangisini yaratadi. Panel atrofidagi error boundary xabar ko'rsatadi. - Kesh — bir ro'yxat (massiv havolasi) — bitta Promise (
WeakMap, use() dagi qoida o'zgarmadi). - Worker yo'q muhit — Vitest (jsdom) da
typeof Worker === "undefined": hisob asosiy oqimda,setTimeoutbilan. Testlar o'zgarmadi.
4.6 postMessage nusxalaydi
postMessage({ id, vazifalar }) ro'yxatni ishchiga nusxalaydi — structured clone algoritmi bilan (Sayoz va chuqur nusxalash). Bu bepul emas: kanon Node'da o'lchadi — 10 000 vazifani structuredClone qilish 5.9 ms, hisobning o'zi esa 26 ms. Nusxa asosiy oqimda bo'ladi, lekin 54 ms lik bloklash 6 ms ga aylandi — foyda aniq.
Katta binar ma'lumot (rasm, audio) uchun nusxa o'rniga transfer bor — ArrayBuffer egaligini ishchiga o'tkazish, nusxasiz (Web Workers dagi transferable). Vazifalar ro'yxati — obyektlar, ularni transfer qilib bo'lmaydi.
4.7 Comlink
O'z Promise o'ramini yozish o'rniga — Comlink kutubxonasi (Google Chrome jamoasidan): ishchida expose(obyekt), asosiy oqimda wrap(worker) — va ishchidagi funksiyalar oddiy async funksiyalar kabi chaqiriladi:
// ishchi.js
import { expose } from "comlink";
expose({ batafsilHisobla });
// asosiy oqim
import { wrap } from "comlink";
const ishchi = wrap(new Worker(manzil, { type: "module" }));
const natija = await ishchi.batafsilHisobla(vazifalar);Id'lar, Map, onmessage — hammasi ichkarida. vazifalar uchun bitta funksiya — kanon 30 qatorli o'z o'ramini qoldirdi (yangi runtime bog'liqlik yo'q). Ko'p funksiyali ishchida Comlink kodni ancha soddalashtiradi.
5. Vazifalar qadami: statistika — Web Worker'da (holat 24)
5.1 O'zgarishlar
Yangi branch: perf/statistika-ishchi. Diff: 5 fayl, +135 −23:
| Fayl | Nima |
|---|---|
statistika-ishchi.ts (yangi) |
ishchi: onmessage → batafsilHisobla → postMessage |
xavfsiz-manzil.ts (yangi) |
bitta Trusted Types siyosati — SW va ishchi manzili |
statistika-vadasi.ts |
setTimeout o'rniga ishchi, id bo'yicha javob, xato bo'lsa — hammasi rad + yangi ishchi |
main.tsx |
SW ro'yxatdan o'tkazish — skriptManzili(SW_MANZILI) |
trusted-types.d.ts |
izoh yangilandi |
Ishchi yaratilishi (statistika-vadasi.ts):
const yangi = new Worker(skriptManzili(ISHCHI_MANZILI) as string, {
name: "statistika",
type: import.meta.env.DEV ? "module" : "classic",
});Ikki nozik joy. Birinchisi — as string: TypeScript'ning DOM turlarida Worker konstruktori TrustedScriptURL ni bilmaydi va uni kengaytirib bo'lmaydi (u var bilan e'lon qilingan) — izohda yozilgan. Ikkinchisi — type: dev'da Vite ishchini ES modul qilib beradi (?worker_file&type=module, natijalar/24-dev.txt), build'da esa bitta oddiy skript (worker.format: "iife").
5.2 O'lchov
olchov/statistika.mjs — 10 000 vazifa, Long Tasks API (50 ms dan uzun vazifalar), 3 urinish (natijalar/24-oldin.txt → 24-keyin.txt):
| holat 23 (asosiy oqim) | holat 24 (Worker) | |
|---|---|---|
| panel ochilishi: uzun vazifalar | [53], [54] (va bitta []) |
[], [], [] |
| katak bosish: uzun vazifalar | [62, 58, 66], [52, 50, 62] |
[57], [52, 57] (va bitta []) |
- holat 23: hisob asosiy oqimda3 urinishdan 2 tasida 53–54 ms54 ms
- holat 24: hisob Web Worker'da3 urinishda ham uzun vazifa yo'q0 ms
Manba: O'lchandi: vazifalar holat 23 va 24, olchov/statistika.mjs (Long Tasks API), Chrome headless, 10 000 vazifa, 3 urinish, 2026-10-13 (natijalar/24-oldin.txt, 24-keyin.txt)
Panel ochilishida uzun vazifa butunlay yo'qoldi. Katak bosishda 3 tadan 1–2 ga kamaydi — qolgani statistika emas: ro'yxatni localStorage uchun JSON qilish va boshqa sinxron ish (TEXNIK-QARZ 7 da yozilgan). Har ikki holatda birinchi urinishda uzun vazifa umuman yo'q — o'lchovda shovqin bor. Shuning uchun jadvalda xom qiymatlar, mediana emas.
Hajm: ishchi alohida fayl — 1.46 KB (gzip 853 B), faqat panel ochilganda yuklanadi; asosiy bundle deyarli o'zgarmadi (natijalar/tekshiruv-24.txt). SW keshidagi qobiq 13 → 14 fayl (ishchi oflayn ham ishlasin).
5.3 Tekshiruvlar va commit
Testlar: node 169, Vitest 39 (o'zgarmadi — jsdom'da asosiy oqim varianti). Brauzer: 159/159, e2e-18 44/44 (yangi: ishchi CSP ostida ishlaydi, oddiy satr bilan — TypeError). Dev: panel ochiladi, CSP buzilishi 0 (24-dev.txt).
perf: statistika hisobi Web Worker'da — Trusted Types siyosati bilan6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| debounce bor, so'rovlar baribir har harfda | debounce render ichida yasalgan | useMemo/useRef, modul darajasi yoki use-debounce |
| komponent yopilgandan keyin so'rov ketdi | taymer tozalanmagan | useEffect tozalovchisida cancel() |
| debounce eski state bilan ishlaydi | eskirgan closure | qiymatni argumentda bering, latest ref |
| scroll'da oxirgi holat yo'qoladi | faqat leading throttle | trailing yoki rAF |
TypeError new Worker(...) da |
Trusted Types siyosati | ?worker&url + siyosatdan o'tkazish |
| javob boshqa so'rovga tushdi | postMessage da id yo'q |
id → resolve Map'i |
| Worker'dan keyin ham UI qotadi | katta nusxa (structuredClone) yoki ish asosiy oqimda qoldi |
o'lchang; transfer yoki kamroq ma'lumot |
7. Mashqlar
Mashqlar kurs/mashqlar/18/24-debounce-worker/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): qaysi vosita?
- Qidiruv maydoni har harfda
fetchyuboradi: - Xaritani sudraganda har
pointermoveda koordinatalar hisoblanadi: - 50 000 qatorli CSV faylni tahlil qilish 300 ms oladi:
- 5 000 taomli ro'yxat xotirada filtrlanadi, maydon qotadi:
Yechim
So'rovlar soni — debounce. Uzluksiz hodisa oqimi — throttle (yoki rAF). Bitta og'ir hisob — Worker (u faylni ham, FileReader natijasini ham oladi). Xotiradagi og'ir render — useDeferredValue: so'rov yo'q, kamaytiradigan chaqiruv yo'q, faqat renderni to'xtatib bo'ladigan qilish kerak.
2-mashq (o'rta): qoralama saqlash
«Bahor» buyurtma izohi localStorage ga saqlanadi (bu yerda — console.log). Hozir har harfda saqlanadi. Shunday qiling: yozish to'xtagandan 400 ms keyin bir marta saqlansin. Tekshiruvchi "a", "ac", "achchiq" yozadi va 600 ms kutadi.
import { useState } from "react";
function saveDraft(text) {
console.log("saqlandi:", text);
}
function App() {
const [note, setNote] = useState("");
function handleChange(e) {
setNote(e.target.value);
saveDraft(e.target.value);
}
return (
<input placeholder="Izoh" value={note} onChange={handleChange} />
);
}Konsolda:
saqlandi: a
saqlandi: ac
saqlandi: achchiqYechim
import { useEffect, useMemo, useState } from "react";
function debounce(fn, ms) {
let timer;
const debounced = (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
debounced.cancel = () => clearTimeout(timer);
return debounced;
}
function saveDraft(text) {
console.log("saqlandi:", text);
}
function App() {
const [note, setNote] = useState("");
const debouncedSave = useMemo(() => debounce(saveDraft, 400), []);
useEffect(() => () => debouncedSave.cancel(), [debouncedSave]);
function handleChange(e) {
setNote(e.target.value);
debouncedSave(e.target.value);
}
return (
<input placeholder="Izoh" value={note} onChange={handleChange} />
);
}Konsolda:
saqlandi: achchiqMaydon har harfda darhol yangilanadi (setNote — debounce'siz), saqlash esa bir marta. Muhim tafsilot: setNote ni debounce qilmang — u holda maydon kechikib, kursor sakraydi (useTransition darsidagi "controlled input shoshilinch" qoidasi). Debounce faqat tashqi ishga.
3-mashq (qiyin): useThrottle hook'i
useThrottledCallback(fn, ms) hook'ini yozing: qaytargan funksiya ms ichida faqat birinchi chaqiruvni bajaradi (leading). Talablar: fn ning eng so'nggi versiyasi chaqirilsin (eskirgan closure bo'lmasin), qaytgan funksiya barqaror bo'lsin. Tekshiruv: «Sevimli» tugmasi 3 marta tez bosiladi, keyin 1 100 ms dan so'ng yana bir marta; har bajarilishda joriy likes soni chiqadi.
function App() {
const [likes, setLikes] = useState(0);
const like = useThrottledCallback(() => {
console.log("bajarildi, oldin:", likes);
setLikes(likes + 1);
}, 1000);
return <button onClick={like}>Sevimli</button>;
}Yechim
import {
useCallback,
useLayoutEffect,
useRef,
useState,
} from "react";
function useThrottledCallback(fn, ms) {
const latest = useRef(fn);
const last = useRef(-Infinity);
useLayoutEffect(() => {
latest.current = fn; // eng so'nggi fn — commit'da
});
return useCallback(
(...args) => {
const now = performance.now();
if (now - last.current < ms) return;
last.current = now;
latest.current(...args);
},
[ms],
);
}
function App() {
const [likes, setLikes] = useState(0);
const like = useThrottledCallback(() => {
console.log("bajarildi, oldin:", likes);
setLikes(likes + 1);
}, 1000);
return <button onClick={like}>Sevimli</button>;
}Konsolda:
bajarildi, oldin: 0
bajarildi, oldin: 1Ikki bajarilish: birinchi bosish va 1 100 ms dan keyingisi. Ikkinchisi likes ni 1 deb ko'rdi — eskirgan 0 emas. Sir — "latest ref": har commit'dan keyin latest.current yangi fn ga almashadi (useDeferredValue darsidagi vazifalar qator hodisalari naqshi). Qaytgan funksiya esa useCallback(…, [ms]) — barqaror, memo li bolaga bersangiz ham buzmaydi. Ref'ga render paytida emas, layout effektda yozamiz — tashlangan render ref'ni buzmasin (Render va commit).
4-mashq: vazifalar qadami
perf/statistika-ishchi branch'ida «Vazifalar qadami» dagi besh faylni o'zgartiring. npm run build — dist/assets/ da statistika-ishchi-<hash>.js paydo bo'lishi kerak. Brauzerda 10 000 vazifa bilan DevTools → Performance yozuvida «Batafsil statistika» ni oching: asosiy oqimda batafsilHisobla qolmaganini va alohida "Worker" oqimida ishlaganini toping. Konsolda new Worker("x.js") ni sinang — qanday xato? Commit va PR.
Yechim
Performance yozuvida pastda yangi qator — Worker: statistika (bu nom { name: "statistika" } dan). Hisob shu qatorda, asosiy oqimda esa faqat postMessage (nusxa, bir necha ms) va javob kelgach React renderi. Konsoldagi new Worker("x.js") — CSP Trusted Types tufayli TypeError (Chrome xabarida "TrustedScriptURL" so'zi bo'ladi): siyosat faqat oq ro'yxatdagi ikki manzilni o'tkazadi. Commit xabari — «Tekshiruvlar va commit» bo'limida.
8. Real ishda
- Qidiruv va avtoto'ldirish: debounce (300–500 ms) + bekor qilish (
AbortController) — har marketplace va xarita servisida. - Muharrirlar: Google Docs, Figma kabi ilovalar avtosaqlashni debounce, kursor/sudrashni throttle (rAF) bilan qiladi.
- Workerlar: Excel/CSV tahlili, rasm siqish, PDF yasash, matnni qidiruv indeksiga aylantirish — UI'ni qotirmaslik uchun ishchida. Next.js va Vite ishchilarni bir xil
new URL(…, import.meta.url)naqshi bilan qo'llaydi. - Intervyu savollari: "Debounce va throttle farqi?" (deyarli har frontend intervyusida, ko'pincha "yozib bering"), "React'da debounce'ni qanday to'g'ri yozasiz?", "Qachon Web Worker?", "Worker bilan ma'lumot almashish narxi?".
Xulosa
- Debounce — chaqiruvlar to'xtagach oxirgisi; throttle — oraliqda bittasi; Worker — og'ir hisob alohida oqimda;
useDeferredValue— og'ir render to'xtatiladigan. - React'da debounce renderlar orasida bitta nusxa bo'lsin (
useMemo, ref, modul darajasi), unmount'dacancel(); eskirgan closure'ga — argument yoki latest ref. vazifalar: qidiruvda debounce yo'q (xotira), qoralamada bor (kechiktir,hoziroq/bekor).- Vite'da Worker:
new URL,?worker,?worker&url; Trusted Types ostida manzil siyosatdan o'tadi. Javob — id bo'yicha Promise'ga; Comlink buni yashiradi. vazifalarholat 24: panel ochilishidagi 53–54 ms uzun vazifa — yo'q; nusxa narxi 5.9 ms, hisob 26 ms (Node).
Keyingi dars: Keng tarqalgan React xatolari katalogi — 18-qism yarmidagi to'xtash: eng ko'p uchraydigan xatolar va ularning belgilari bir joyda.
Manbalar
- React:
useMemo— "Memoizing a function" — react.dev/reference/react/useMemo - use-debounce — github.com/xnimorz/use-debounce
- Vite: "Web Workers" — vite.dev/guide/features#web-workers
- MDN: Trusted Types API — developer.mozilla.org/docs/Web/API/Trusted_Types_API
- Comlink — github.com/GoogleChromeLabs/comlink
- Kanon:
sinov/18/QADAMLAR.md(#24),natijalar/24-oldin.txt,24-keyin.txt,24-dev.txt,natijalar/tekshiruv-24.txt
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!