Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. Cleanup funksiyasi
- 2.1 Taymerni to'xtatish
- 2.2 Cleanup bo'lmasa — tinglovchilar ko'payadi
- 3. Cleanup qachon chaqiriladi
- 3.1 Tartib
- 3.2 Nima tozalanadi
- 4. Race condition: kech kelgan javob
- 4.1 Jonli muammo
- 4.2 Birinchi davo: ignore bayrog'i
- 4.3 Ikkinchi davo: AbortController
- 4.4 Effektda ma'lumot olish — bu boshlanishi
- 5. Vazifalar qadami: tashqi tizimlar effektlarda
- 5.1 Nima o'zgaradi
- 5.2 Boshqa tablar: obunani bekor qilish
- 5.3 Server: AbortController
- 5.4 Internet va URL
- 5.5 Commit va tekshiruvlar
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): konsol tartibi
- 2-mashq (o'rta): taymerni tozalang
- 3-mashq (qiyin): taom tafsiloti, poygasiz
- 4-mashq: Vazifalar qadami
- 8. Real ishda
- Xulosa
- Manbalar
useEffect III: cleanup, race condition va AbortController — effekt boshlagan ishni tozalash
Qisqacha: Effekt funksiyasi cleanup (tozalash) funksiyasini qaytarishi mumkin:
useEffect(() => { const id = setInterval(…); return () => clearInterval(id); }, []). React uni ikki paytda chaqiradi: effekt yangi qiymatlar bilan qayta ishlashidan oldin va komponent ekrandan olib tashlanganda. Qoida: effekt nima boshlasa (taymer, tinglovchi, obuna, so'rov) — cleanup o'shani to'xtatadi. Cleanup bo'lmasa, tinglovchilar ko'payib boradi va kech kelgan server javobi yangi javobning ustiga yoziladi (race condition). So'rov uchun eng yaxshi cleanup —AbortController:return () => controller.abort().
Bu darsda
- Cleanup funksiyasini yozasiz va u qachon chaqirilishini konsolda kuzatasiz.
setIntervalvaaddEventListenerni effektda to'g'ri tozalaysiz.- Race condition'ni jonli ko'rasiz: tez javob sekin javobdan oldin keladi va ekran yolg'on gapiradi.
- Uni ikki usulda tuzatasiz:
ignorebayrog'i vaAbortController. vazifalardagi tashqi tizimlarni (boshqa tablar, server, internet, URL) cleanup bilan effektlarga ko'chirasiz.
Oldin bilishingiz kerak: useEffect II, Taymerlar: setTimeout va setInterval, Bekor qilish: AbortController, fetch asoslari.
1. Nega bu kerak?
Oldingi darsda massiv effektning qachon qayta ishlashini belgilashini ko'rdik. Lekin effekt qayta ishlaganda, oldingisi boshlagan ish nima bo'ladi?
Sardor «Bahor» menyusiga ikki narsa qo'shdi. Birinchisi — oshxona soati: har soniyada vaqt yangilanadi. Ikkinchisi — kategoriya tugmalari: «Taom» yoki «Ichimlik» bosilsa, serverdan o'sha bo'lim yuklanadi. Uchinchi kuni Jasur aka ikki shikoyat bilan keldi. Birinchisi: "Telefonim qiziyapti, sahifa sekinlashdi". Ikkinchisi: "Men «Ichimlik» ni bosdim, tepada «Tanlangan: ichimlik» deb turibdi, ro'yxatda esa osh va manti!"
Ikkala muammoning sababi bitta: effekt ishni boshladi, lekin hech kim uni to'xtatmadi. Yopilgan soatning taymeri ishlashda davom etdi. Eski kategoriya uchun yuborilgan so'rov esa yangisidan kechroq qaytib, ekranni bosib ketdi.
Hayotiy o'xshatish: mehmonxona xonasi. Mehmon kirganda chiroq, konditsioner va televizorni yoqadi (effekt). Chiqib ketayotganda — o'chiradi (cleanup). Keyingi mehmon toza xonaga kiradi. Agar hech kim o'chirmasa, har yangi mehmon bilan yoqilgan asboblar soni ko'payib boradi va hisob kelganda hamma hayron bo'ladi.
2. Cleanup funksiyasi
2.1 Taymerni to'xtatish
Effekt funksiyasi funksiya qaytarsa, React uni eslab qoladi — bu cleanup (tozalash) funksiyasi. Uni React o'zi, kerakli paytda chaqiradi. Soat:
import { useEffect, useState } from "react";
function Clock() {
const [ticks, setTicks] = useState(0);
useEffect(() => {
console.log("taymer boshlandi");
const id = setInterval(() => {
console.log("tik");
setTicks((t) => t + 1);
}, 250);
return () => {
clearInterval(id); // effekt boshlagan ishni to'xtatamiz
console.log("taymer to'xtadi");
};
}, []);
return <p>Tik: {ticks}</p>;
}
function App() {
const [open, setOpen] = useState(true);
return (
<div>
{open && <Clock />}
<button onClick={() => setOpen(false)}>Yopish</button>
</div>
);
}Konsolda:
taymer boshlandi
tik
tik
taymer to'xtadiEkranda:
YopishBlok 600 ms kutadi (ikki "tik"), keyin «Yopish» ni bosadi. Clock ekrandan olib tashlandi — React cleanup'ni chaqirdi, taymer to'xtadi. Shundan keyin konsolda boshqa "tik" yo'q.
Ikkita mayda, lekin muhim detal:
setTicks((t) => t + 1)— updater (State II). Taymer ichidagi funksiya bir marta yaratilgan vaticksning birinchi qiymatini (0) ko'radi.setTicks(ticks + 1)yozsak, soat 1 da qotib qolardi. Updater esa har safar eng so'nggi qiymatni oladi. Shuning uchun massiv[]— halol: effektticksni o'qimaydi.id— effekt ichidagi o'zgaruvchi. Cleanup funksiyasi closure orqali aynan shu effektningidsini ko'radi. Har effektning o'zidsi, o'z cleanup'i bor.
2.2 Cleanup bo'lmasa — tinglovchilar ko'payadi
Endi xatoni ko'ramiz. Taymer bilan ham ko'rsatsa bo'lardi, lekin vaqtga bog'liq natija har kompyuterda boshqacha. Shuning uchun window ga bosish tinglovchisini olamiz — natija aniq. Effekt category ga bog'liq va cleanup qaytarmaydi:
import { useEffect, useState } from "react";
function App() {
const [category, setCategory] = useState("taom");
useEffect(() => {
function handleClick() {
console.log("bosildi, kategoriya:", category);
}
window.addEventListener("click", handleClick); // ❌ cleanup yo'q
}, [category]);
return (
<div>
<button onClick={() => setCategory("taom")}>Taom</button>{" "}
<button onClick={() => setCategory("ichimlik")}>
Ichimlik
</button>{" "}
<button>Hisob</button>
</div>
);
}Konsolda:
bosildi, kategoriya: taom
bosildi, kategoriya: taom
bosildi, kategoriya: ichimlik
bosildi, kategoriya: taom
bosildi, kategoriya: ichimlik
bosildi, kategoriya: taomUch bosish — olti qator. Birinchi bosishda bitta tinglovchi bor edi, ikkinchisida — ikkita, uchinchisida — uchta. Har category o'zgarishi yangi tinglovchi qo'shdi, eskisini hech kim olib tashlamadi. Yana yomoni: eski tinglovchilar eski category ni ko'radi (har biri o'z closure'ida). Bir soatdan keyin yuzlab tinglovchi bo'ladi — telefon shu sababli qiziydi.
Tuzatish — bitta qator:
import { useEffect, useState } from "react";
function App() {
const [category, setCategory] = useState("taom");
useEffect(() => {
function handleClick() {
console.log("bosildi, kategoriya:", category);
}
window.addEventListener("click", handleClick);
return () => window.removeEventListener("click", handleClick);
}, [category]);
return (
<div>
<button onClick={() => setCategory("taom")}>Taom</button>{" "}
<button onClick={() => setCategory("ichimlik")}>
Ichimlik
</button>{" "}
<button>Hisob</button>
</div>
);
}Konsolda:
bosildi, kategoriya: taom
bosildi, kategoriya: ichimlik
bosildi, kategoriya: taomHar bosishda bitta qator, har doim joriy kategoriya. removeEventListener ga aynan o'sha funksiya beriladi (addEventListener asoslari darsidagi qoida). handleClick effekt ichida e'lon qilingani uchun cleanup uni closure orqali biladi.
Tekshirib ko'ring: Konsolning birinchi qatorida nega
taomturibdi? Axir birinchi bosilgan tugma «Ichimlik» edi.
Javob
Bosish hodisasi avval tugmaning onClick ini chaqiradi (setCategory("ichimlik")), keyin window gacha ko'tariladi. Shu paytda window da hali eski tinglovchi turibdi — u chizishdan oldingi category ni (taom) ko'radi. Qayta chizish va effekt almashinuvi hodisa tugagandan keyin bo'ladi. Ikkinchi qatordan boshlab tinglovchi yangi.
3. Cleanup qachon chaqiriladi
3.1 Tartib
Cleanup ikki paytda ishlaydi:
- Effekt qayta ishlashidan oldin — massivdagi qiymat o'zgardi: avval eski effektning cleanup'i, keyin yangi effekt.
- Komponent ekrandan olib tashlanganda — oxirgi effektning cleanup'i.
import { useEffect, useState } from "react";
function Menu({ category }) {
useEffect(() => {
console.log("effekt (setup):", category);
return () => console.log("cleanup:", category);
}, [category]);
console.log("render:", category);
return <p>Menyu: {category}</p>;
}
function App() {
const [category, setCategory] = useState("taom");
const [open, setOpen] = useState(true);
return (
<div>
{open && <Menu category={category} />}
<button onClick={() => setCategory("ichimlik")}>
Ichimlik
</button>{" "}
<button onClick={() => setOpen(false)}>Yopish</button>
</div>
);
}Konsolda:
render: taom
effekt (setup): taom
render: ichimlik
cleanup: taom
effekt (setup): ichimlik
cleanup: ichimlikHar qatorni bosqichlarga joylaymiz:
| Render | Commit | Cleanup | Effekt |
|---|---|---|---|
ochilish: taom |
DOM yaratildi | — | setup taom |
«Ichimlik»: ichimlik |
matn yangilandi | taom |
setup ichimlik |
«Yopish»: App (Menu'siz) |
<p> olib tashlandi |
ichimlik |
— |
Ikkinchi qatorga qarang: cleanup yangi chizishdan keyin, lekin yangi effektdan oldin ishladi. U eski qiymatni (taom) ko'radi — chunki u eski effektning closure'i. Effekt va uning cleanup'i — juftlik: bitta chizishdan, bir xil qiymatlar bilan.
sequenceDiagram
participant R as React
participant E1 as Effekt (taom)
participant E2 as Effekt (ichimlik)
R->>E1: setup — ulanish taom
Note over R: category o'zgardi, qayta chizish
R->>E1: cleanup — uzish taom
R->>E2: setup — ulanish ichimlik
Note over R: Menu yopildi
R->>E2: cleanup — uzish ichimlikDiagrammada har effektning hayoti ko'rinadi: u "boshlanadi" va "to'xtaydi", keyingisi boshlanishidan oldin. Bu "boshla — to'xtat" juftligi keyingi darsning asosiy g'oyasi.
3.2 Nima tozalanadi
Qoida oddiy: effekt boshlagan har narsa — cleanup'da to'xtatiladi. Effekt nimani boshlasa, xuddi o'sha narsa to'xtatiladi:
| Effektda | Cleanup'da |
|---|---|
setInterval / setTimeout |
clearInterval / clearTimeout |
addEventListener(tur, fn) |
removeEventListener(tur, fn) |
kanal.addEventListener("message", fn) |
kanal.removeEventListener(…) yoki kanal.close() |
new WebSocket(url) |
socket.close() |
fetch(url, { signal }) |
controller.abort() |
tashqi kutubxona: map = new Map(el) |
map.destroy() (kutubxonada qanday atalsa) |
document.title va localStorage ga yozish — cleanup talab qilmaydi: ular "boshlanib, davom etadigan" ish emas. Keyingi effekt ularni shunchaki ustidan yozadi.
Tekshirib ko'ring:
useEffect(() => { setTimeout(() => console.log("salom"), 3000); }, [])— komponent 1 soniyadan keyin yopilsa, konsolda "salom" chiqadimi? Qanday tuzatasiz?
Javob
Chiqadi: taymer brauzerda ishlayveradi, React u haqda hech narsa bilmaydi. Tuzatish: const id = setTimeout(…); return () => clearTimeout(id);. Taymer ichida setState bo'lsa, bu yanada muhim — yopilgan komponent uchun yangilanish keraksiz ish.
4. Race condition: kech kelgan javob
4.1 Jonli muammo
Jasur akaning ikkinchi shikoyati. Serverdan menyu olish — vaqt talab qiladi, javob tezligi esa har xil. Kurs serveri ?kechikish= parametri bilan javobni ataylab sekinlashtiradi. Biz shu bilan "taom sekin (1,5 s), ichimlik tez (0,3 s)" holatini yasadik. Kod — cleanup'siz:
import { useEffect, useState } from "react";
const API = "https://ilmhamroh.uz/api/mashq/menyu";
function App() {
const [category, setCategory] = useState("taom");
const [dishes, setDishes] = useState([]);
useEffect(() => {
// taom sekin (1500 ms), ichimlik tez (300 ms) — ataylab
const delay = category === "taom" ? 1500 : 300;
const url = `${API}?turkum=${category}&kechikish=${delay}`;
console.log("so'rov:", category);
fetch(url)
.then((res) => res.json())
.then((list) => {
console.log("javob:", category, list.length, "ta");
setDishes(list);
});
}, [category]);
return (
<div>
<p>Tanlangan: {category}</p>
<ul>{dishes.map((d) => <li key={d.id}>{d.nom}</li>)}</ul>
<button onClick={() => setCategory("ichimlik")}>
Ichimlik
</button>
</div>
);
}«Ishga tushir» ni bosing va 1,5 soniya ichida «Ichimlik» ni bosing. Biz buni Chrome 154 da avtomatik qildik (ochilgandan 300 ms keyin bosish):
so'rov: taom
so'rov: ichimlik
javob: ichimlik 1 ta
javob: taom 3 ta
Ekranda: Tanlangan: ichimlik
Osh
Lag'mon
MantiTanlangan — ichimlik, ro'yxatda — taomlar. Vaqt chizig'ida nima bo'lgani:
| Vaqt | Voqea | dishes |
|---|---|---|
| 0 ms | so'rov taom (1 500 ms) |
[] |
| 300 ms | «Ichimlik» → so'rov ichimlik (300 ms) |
[] |
| ~600 ms | javob ichimlik keldi → setDishes |
Ko'k choy |
| ~1 500 ms | javob taom keldi → setDishes |
Osh, Lag'mon, Manti |
Race condition (poyga holati) — natija ishlar qaysi tartibda tugashiga bog'liq bo'lib qoladigan xato (fetch bilan race condition — 11-qismdan tanish). Ikki so'rov "poygaga chiqdi" va kechroq yuborilgani birinchi keldi. Tarmoqda bu oddiy hol: Wi-Fi, mobil internet, server yuklamasi.
Bu xatoning yomon tomoni — u sizning kompyuteringizda ko'pincha ko'rinmaydi. Lokal serverda hamma javob bir xil tez. Uni mehmon Chilonzordagi sekin 3G'da topadi.
4.2 Birinchi davo: ignore bayrog'i
Cleanup "bu effekt eskirdi" degan belgi qo'yishi mumkin. Effekt ichida let ignore = false, cleanup'da — true. Javob kelganda bayroqni tekshiramiz. Endi jonli blok vaqtga bog'liq bo'lmasligi uchun haqiqiy fetch o'rniga uning kichik nusxasini yozamiz: fetchMenu — xuddi shu kechikishlar bilan javob beradigan Promise (Promise asoslari).
import { useEffect, useState } from "react";
const MENU = [
{ id: 1, nom: "Osh", turkum: "taom" },
{ id: 2, nom: "Lag'mon", turkum: "taom" },
{ id: 4, nom: "Ko'k choy", turkum: "ichimlik" },
];
// fetch o'rnida: taom — 600 ms, ichimlik — 200 ms
function fetchMenu(category) {
const delay = category === "taom" ? 600 : 200;
return new Promise((resolve) => {
setTimeout(() => {
resolve(MENU.filter((d) => d.turkum === category));
}, delay);
});
}
function App() {
const [category, setCategory] = useState("taom");
const [dishes, setDishes] = useState([]);
useEffect(() => {
let ignore = false; // bu effekt eskirdimi?
console.log("so'rov:", category);
fetchMenu(category).then((list) => {
if (ignore) {
console.log("e'tiborsiz:", category);
return;
}
console.log("javob:", category);
setDishes(list);
});
return () => {
ignore = true;
};
}, [category]);
return (
<div>
<p>Tanlangan: {category}</p>
<ul>{dishes.map((d) => <li key={d.id}>{d.nom}</li>)}</ul>
<button onClick={() => setCategory("ichimlik")}>
Ichimlik
</button>
</div>
);
}Konsolda:
so'rov: taom
so'rov: ichimlik
javob: ichimlik
e'tiborsiz: taomEkranda:
Tanlangan: ichimlik
Ko'k choy
Ichimliktaom javobi keldi, lekin uning effekti allaqachon "eskirgan" — ignore cleanup'da true bo'lgan. Har effektning o'z ignore o'zgaruvchisi bor (closure), shuning uchun yangi effektning bayrog'iga tegilmaydi. React hujjatidagi rasmiy naqsh aynan shu.
4.3 Ikkinchi davo: AbortController
ignore javobni e'tiborsiz qoldiradi, lekin so'rov baribir oxirigacha boradi: trafik sarflanadi, server ishlaydi. AbortController esa so'rovning o'zini to'xtatadi. fetch ning ikkinchi argumentiga signal beriladi. Cleanup'da controller.abort() chaqiriladi — va fetch qaytargan Promise AbortError bilan rad etiladi.
Jonli blok uchun fetchMenu ga ham signal ni o'rgatamiz — xuddi fetch kabi: abort bo'lsa, taymerni to'xtatib, signal.reason bilan rad etadi:
import { useEffect, useState } from "react";
const MENU = [
{ id: 1, nom: "Osh", turkum: "taom" },
{ id: 2, nom: "Lag'mon", turkum: "taom" },
{ id: 4, nom: "Ko'k choy", turkum: "ichimlik" },
];
// fetch o'rnida, signal bilan: abort bo'lsa — AbortError
function fetchMenu(category, signal) {
const delay = category === "taom" ? 600 : 200;
return new Promise((resolve, reject) => {
const id = setTimeout(() => {
resolve(MENU.filter((d) => d.turkum === category));
}, delay);
signal.addEventListener("abort", () => {
clearTimeout(id);
reject(signal.reason);
});
});
}
function App() {
const [category, setCategory] = useState("taom");
const [dishes, setDishes] = useState([]);
useEffect(() => {
const controller = new AbortController();
console.log("so'rov:", category);
fetchMenu(category, controller.signal)
.then((list) => {
console.log("javob:", category);
setDishes(list);
})
.catch((error) => {
if (error.name === "AbortError") {
console.log("bekor qilindi:", category);
return;
}
throw error;
});
return () => controller.abort();
}, [category]);
return (
<div>
<p>Tanlangan: {category}</p>
<ul>{dishes.map((d) => <li key={d.id}>{d.nom}</li>)}</ul>
<button onClick={() => setCategory("ichimlik")}>
Ichimlik
</button>
</div>
);
}Konsolda:
so'rov: taom
so'rov: ichimlik
bekor qilindi: taom
javob: ichimlikEkranda:
Tanlangan: ichimlik
Ko'k choy
IchimlikEndi taom so'rovi javobini kutmasdan, «Ichimlik» bosilgan zahoti to'xtadi. AbortError — kutilgan holat, xato emas: biz uni catch da tanib, jim chiqamiz. Boshqa xatolar (tarmoq uzildi, server 500) esa yashirilmaydi.
Haqiqiy fetch bilan — xuddi shunday, faqat fetchMenu(category, controller.signal) o'rniga fetch(url, { signal: controller.signal }). Chrome 154 da haqiqiy server bilan tekshirdik (o'sha 1 500 / 300 ms):
so'rov: taom
so'rov: ichimlik
bekor qilindi: taom — signal is aborted without reason
javob: ichimlik 1 taerror.message — "signal sababsiz bekor qilindi": abort() ga sabab berilmagan. DevTools'ning Network panelida bekor qilingan so'rov (canceled) holati bilan ko'rinadi.
ignore bayrog'i |
AbortController |
|
|---|---|---|
| eski javob ekranga tushadimi | yo'q | yo'q |
| eski so'rov to'xtaydimi | yo'q, oxirigacha boradi | ha |
| qayerda ishlaydi | har qanday Promise | fetch va signal qabul qiladigan API |
catch da nima |
hech narsa | AbortError ni ajratish |
Maslahat: Ikkalasini birga ham ishlatish mumkin.
fetchning javobi kelgandan keyin ham ish bo'lsa (await res.json()dan keyin yanaawait),signal.abortedni tekshirish —ignorening o'zi.
4.4 Effektda ma'lumot olish — bu boshlanishi
Bugungi kod to'g'ri, lekin to'liq emas. "Yuklanmoqda…" holati, xato holati, kesh (ortga qaytganda qayta yuklamaslik), sahifadagi ketma-ket so'rovlar — bularning hammasini qo'lda yozish og'ir. Nega effektda ma'lumot olish ko'pincha eng yaxshi yo'l emasligini Effektda ma'lumot olish darsida ko'ramiz.
5. Vazifalar qadami: tashqi tizimlar effektlarda
5.1 Nima o'zgaradi
refactor/effektlar branch'idagi ishni tugatamiz. useEffect I darsida holat App ga ko'chdi va localStorage effekti yozildi. useEffect II darsida massivlarni tahlil qildik. Bugun — qolgan to'rt tashqi tizim, hammasi cleanup bilan. Keyin asosiy.ts (vanilla tinglovchilar) va render.tsx (ko'prik) o'chadi.
| Tashqi tizim | v6 / holat 24 (asosiy.ts) |
holat 28 (App.tsx) |
|---|---|---|
| boshqa tablar | tingla(boshqaTabdanYangila) — abadiy |
effekt, cleanup — obunani bekor qiladi |
| server | void sinxronla() modul oxirida |
effekt, cleanup — AbortController |
| internet | addEventListener("online", …) |
effekt, cleanup — removeEventListener |
| URL (Orqaga) | addEventListener("popstate", …) |
effekt, cleanup |
5.2 Boshqa tablar: obunani bekor qilish
v6 dagi tingla (sinxron.ts) tinglovchini qo'shardi va hech narsa qaytarmasdi (: void) — vanilla ilovada obuna sahifa yopilguncha yashardi. Komponentda esa obunani to'xtatish kerak. Shuning uchun tingla endi obunani bekor qiluvchi funksiya qaytaradi (kanon, aynan):
-export function tingla(ishlovchi: (xabar: SinxronXabar) => void): void {
- kanal.addEventListener("message", (event) => {
+export function tingla(
+ ishlovchi: (xabar: SinxronXabar) => void,
+): () => void {
+ const tinglovchi = (event: MessageEvent) => {
if (xabarmi(event.data)) {
ishlovchi(event.data);
}
- });
+ };
+ kanal.addEventListener("message", tinglovchi);
+ return () => kanal.removeEventListener("message", tinglovchi);
}Ishlovchi endi nomli o'zgaruvchida (tinglovchi) — removeEventListener ga aynan o'sha funksiya kerak. Effekt (switch ichi qisqartirildi):
// #28: boshqa tablar. Cleanup — obunani bekor qiladi. Kelgan ro'yxat
// saqlanadi (effekt yuqorida), lekin QAYTA YUBORILMAYDI: yuborsak,
// tablar bir-biriga cheksiz javob qaytaradi (aks-sado, 17/#30)
useEffect(
() =>
tingla((xabar) => {
switch (xabar.tur) {
case OZGARDI: { … setVazifalar(…); … }
}
}),
[],
);Arrow funksiya () => tingla(…) — tingla nima qaytarsa, effekt ham shuni qaytaradi. tingla esa () => kanal.removeEventListener("message", tinglovchi) qaytaradi. Demak cleanup — tayyor. Izohdagi "aks-sado" — Sizga effekt kerak emas darsidagi o'lchov mavzusi.
5.3 Server: AbortController
server-sinxron.ts dagi sinxronla har yangi chaqiruvda eski so'rovni bekor qiladi (sinxronPulti?.abort()) va o'zining AbortController ini yaratadi. Effekt uni chaqiradi, cleanup esa yo'ldagi so'rovni to'xtatadi (kanon, aynan):
// #28: server — ochilganda bir marta (boshlangichRoyxat o'zgarmaydi).
// Cleanup so'rovni bekor qiladi (AbortController): komponent yopilsa
// yoki StrictMode dev'da effektni sinab, qayta ishga tushirsa
useEffect(() => {
void sinxronla(boshlangichRoyxat, {
setVazifalar,
setTarmoq,
setTahrirId,
});
return sinxronniTox;
}, [boshlangichRoyxat]);return sinxronniTox — funksiyaning o'zi (chaqiruv emas). Bekor qilingan so'rovni sinxronla tanib, jim chiqadi: if (xato instanceof ApiXatosi && xato.kod === "BEKOR") return; — «AbortController» bo'limidagi AbortError tekshiruvining kanondagi shakli.
Izohdagi "StrictMode dev'da" ga e'tibor bering. npm run dev da haqiqiy tarmoq jurnalini yozib oldik. Har qatorning boshidagi so'zga qarang — BEKOR yoki 200:
BEKOR GET /api/mashq/vazifalar/de96… — net::ERR_ABORTED ← 1-effekt, cleanup bekor qildi
200 GET /api/mashq/vazifalar/de96…
200 PUT /api/mashq/vazifalar/de96… ← server kalitni bilmadi — ro'yxat yuborildiBitta ochilishda ikki GET: birinchisi bekor qilingan. Bu StrictMode — u dev'da effektni ataylab "boshla — to'xtat — yana boshla" qiladi. Nega va nima uchun bu foydali — keyingi darsda. Hozircha xulosa: cleanup to'g'ri yozilgani uchun takror zararsiz bo'ldi — ikkinchi PUT yuborilmadi.
5.4 Internet va URL
Brauzer hodisalari — odatdagi juftlik (kanon):
// URL: Orqaga/Oldinga — filtr va saralash qayta o'qiladi
useEffect(() => {
function orqaga() {
setFiltr(filtrniOl());
setSaralash(saralashniOl());
setTahrirId(null);
}
addEventListener("popstate", orqaga);
return () => removeEventListener("popstate", orqaga);
}, []);Internet effekti xuddi shunday tuzilgan (offline va online — ikki tinglovchi, ikkalasi cleanup'da olib tashlanadi), faqat massivi — [vazifalar] (useEffect II darsidagi jadval). pagehide effekti — kutib turgan qoralamani sahifa yopilganda saqlaydi.
5.5 Commit va tekshiruvlar
Uch darsdagi ish (holat App ga, localStorage effekti, massivlar va bugungi to'rt effekt) kanonda bitta commit — biz uni o'qish oson bo'lsin deb uch darsga bo'ldik. asosiy.ts va render.tsx o'chirildi. Fokus yordamchilari fokus.ts ga, server mantiqi server-sinxron.ts ga ko'chdi, holat.ts da faqat sof funksiyalar qoldi.
git add -A
git commitCommit (kanon): refactor: holat App state'ida, tashqi tizimlar — effektlar. Body: asosiy.ts, render.tsx o'chirildi; main.tsx — kirish. App: useState (vazifalar, filtr, saralash, qidiruv, tahrirId, tarmoq, e'lon, dialog). Effektlar: localStorage, BroadcastChannel (cleanup), server (AbortController), online/offline, popstate, pagehide.
Tekshiruvlar (haqiqiy):
npm test— node:testℹ tests 147 · ℹ pass 147, VitestTests 22 passed (22). Holat testlari sof funksiyalarga ko'chdi.npm run lint,format:check,tip—exit 0. Build:dist/assets/index-Dt1vjao_.js 248.41 kB │ gzip: 78.74 kB.- Brauzer tekshiruvi — 159/159 birinchi urinishda (oflayn, boshqa tab, dialog, CSP ham).
- Diff: 18 fayl, +928 −1016 — o'chirilgan qatorlar qo'shilganidan ko'p.
Bitta haqiqiy tuzoq topildi. server-sinxron.ts api ni modul yuklanganda yaratadi va globalThis.fetch ni o'shanda oladi. Vitest'dagi vi.stubGlobal("fetch", …) esa kech qoldi — test haqiqiy fetch bilan undefined/vazifalar/… ga borib, Sinxron bo'lmadi: Internet aloqasini tekshiring. dedi. Soxta fetch vitest-sozlash.ts ga, ya'ni har test faylidan oldin ishlaydigan joyga ko'chirildi (TEXNIK-QARZ 36). Testlar chuqur — Frontend testlash qismida.
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| bir bosishda bir necha qator, vaqt o'tib ko'payadi | addEventListener bor, cleanup yo'q |
return () => removeEventListener(…) |
| yopilgan komponentning taymeri ishlayapti | setInterval tozalanmagan |
return () => clearInterval(id) |
| tanlangan bitta, ro'yxat boshqa | race condition | ignore yoki AbortController |
konsolda AbortError qizil |
catch abort'ni ajratmaydi |
if (error.name === "AbortError") return; |
| soat 1 da qotdi | taymerda setX(x + 1), massiv [] |
updater: setX((x) => x + 1) |
removeEventListener ishlamaydi |
boshqa funksiya berildi (() => … yangidan) |
funksiyani o'zgaruvchida saqlang |
useEffect must not return anything besides a function |
useEffect(async () => …) — Promise qaytdi |
ichida async funksiya e'lon qilib chaqiring |
Oxirgi qator haqida batafsil. useEffect(async () => { … }) yozsangiz, effekt funksiyasi Promise qaytaradi. React esa qaytgan qiymatni cleanup deb kutadi va konsolda (qisqartirildi) shunday deydi:
useEffect must not return anything besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately:"useEffect tozalash uchun ishlatiladigan funksiyadan boshqa hech narsa qaytarmasligi kerak. useEffect(async () => …) yozganga o'xshaysiz. O'rniga async funksiyani effekt ichida yozib, darhol chaqiring." To'g'ri shakl:
useEffect(() => {
const controller = new AbortController();
async function load() {
const res = await fetch(url, { signal: controller.signal });
setDishes(await res.json());
}
load().catch(() => {}); // xatolarni ushlash — keyinroq to'liq
return () => controller.abort();
}, [url]);7. Mashqlar
Mashqlar kurs/mashqlar/17/28-cleanup-race/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): konsol tartibi
Kodni ishga tushirmasdan, «Manti» va keyin «Yopish» bosilgandan keyingi konsolni yozing.
import { useEffect, useState } from "react";
function Order({ dish }) {
useEffect(() => {
console.log("buyurtma:", dish);
return () => console.log("bekor:", dish);
}, [dish]);
return <p>{dish}</p>;
}
function App() {
const [dish, setDish] = useState("Osh");
const [open, setOpen] = useState(true);
return (
<div>
{open && <Order dish={dish} />}
<button onClick={() => setDish("Manti")}>Manti</button>{" "}
<button onClick={() => setOpen(false)}>Yopish</button>
</div>
);
}- 2-qator:
- 4-qator:
Yechim
Konsolda:
buyurtma: Osh
bekor: Osh
buyurtma: Manti
bekor: Manti«Manti» — avval eski effektning cleanup'i (Osh), keyin yangi effekt. «Yopish» — oxirgi effektning cleanup'i. Cleanup har doim o'z effektining qiymatini ko'radi.
2-mashq (o'rta): taymerni tozalang
Buyurtma tayyor bo'lishiga qolgan soniyalarni ko'rsatuvchi komponent. Blok 700 ms kutib, «Bekor qilish» ni bosadi. Hozir komponent yopilgandan keyin ham taymer ishlayveradi va har safar qoldi: yozadi. Effektni tuzating: taymer cleanup'da to'xtasin va to'xtaganini console.log("taymer to'xtadi") bilan bildirsin. Updater haqida «Taymerni to'xtatish» bo'limiga qarang.
import { useEffect, useState } from "react";
function Countdown() {
const [left, setLeft] = useState(10);
useEffect(() => {
setInterval(() => {
console.log("qoldi:");
setLeft((l) => l - 1);
}, 300);
}, []);
return <p>Tayyor bo'lishiga: {left} s</p>;
}
function App() {
const [open, setOpen] = useState(true);
return (
<div>
{open && <Countdown />}
<button onClick={() => setOpen(false)}>Bekor qilish</button>
</div>
);
}Yechim
import { useEffect, useState } from "react";
function Countdown() {
const [left, setLeft] = useState(10);
useEffect(() => {
const id = setInterval(() => {
console.log("qoldi:");
setLeft((l) => l - 1);
}, 300);
return () => {
clearInterval(id);
console.log("taymer to'xtadi");
};
}, []);
return <p>Tayyor bo'lishiga: {left} s</p>;
}
function App() {
const [open, setOpen] = useState(true);
return (
<div>
{open && <Countdown />}
<button onClick={() => setOpen(false)}>Bekor qilish</button>
</div>
);
}Konsolda:
qoldi:
qoldi:
taymer to'xtadiEkranda:
Bekor qilish700 ms ichida ikki "qoldi:" (300 va 600 ms da), keyin cleanup. Oxirgi kut(400) davomida boshqa qator yo'q — taymer haqiqatan to'xtadi. setInterval qaytargan raqamni id ga saqlash shart: busiz to'xtatib bo'lmaydi.
3-mashq (qiyin): taom tafsiloti, poygasiz
DishInfo taom id si bo'yicha ma'lumot oladi. Kodda race condition bor: blok 1 dan keyin tezda 4 ni tanlaydi, 1 ning javobi esa sekinroq keladi. AbortController bilan tuzating: fetchDish(id, signal) ga signal bering, cleanup'da abort qiling va AbortError ni jim o'tkazing (konsolga bekor: 1 yozing). Ekranda 4 ning ma'lumoti qolsin. Ishora: «Ikkinchi davo: AbortController» bo'limidagi fetchMenu — xuddi shunday.
import { useEffect, useState } from "react";
const DISHES = {
1: { nom: "Osh", narx: 35000 },
4: { nom: "Ko'k choy", narx: 5000 },
};
function fetchDish(id, signal) {
const delay = id === 1 ? 500 : 100; // osh — sekin
return new Promise((resolve, reject) => {
const timer = setTimeout(() => resolve(DISHES[id]), delay);
signal?.addEventListener("abort", () => {
clearTimeout(timer);
reject(signal.reason);
});
});
}
function App() {
const [id, setId] = useState(1);
const [dish, setDish] = useState(null);
useEffect(() => {
fetchDish(id).then((data) => {
console.log("javob:", id);
setDish(data);
});
}, [id]);
const text = dish
? `${dish.nom} — ${dish.narx} so'm`
: "Yuklanmoqda…";
return (
<div>
<p>{text}</p>
<button onClick={() => setId(4)}>Ko'k choy</button>
</div>
);
}Konsolda:
javob: 4
javob: 1Ekranda:
Osh — 35000 so'm
Ko'k choyYechim
import { useEffect, useState } from "react";
const DISHES = {
1: { nom: "Osh", narx: 35000 },
4: { nom: "Ko'k choy", narx: 5000 },
};
function fetchDish(id, signal) {
const delay = id === 1 ? 500 : 100; // osh — sekin
return new Promise((resolve, reject) => {
const timer = setTimeout(() => resolve(DISHES[id]), delay);
signal?.addEventListener("abort", () => {
clearTimeout(timer);
reject(signal.reason);
});
});
}
function App() {
const [id, setId] = useState(1);
const [dish, setDish] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetchDish(id, controller.signal)
.then((data) => {
console.log("javob:", id);
setDish(data);
})
.catch((error) => {
if (error.name === "AbortError") {
console.log("bekor:", id);
return;
}
throw error;
});
return () => controller.abort();
}, [id]);
const text = dish
? `${dish.nom} — ${dish.narx} so'm`
: "Yuklanmoqda…";
return (
<div>
<p>{text}</p>
<button onClick={() => setId(4)}>Ko'k choy</button>
</div>
);
}Konsolda:
bekor: 1
javob: 4Ekranda:
Ko'k choy — 5000 so'm
Ko'k choy1 ning so'rovi «Ko'k choy» bosilgan zahoti to'xtadi — uning javobi umuman kelmadi. Shuni ham sezing: tanlov almashganda eski taom bir lahza ekranda qoladi, keyin yangisi keladi. "Yuklanmoqda…" ni har almashishda ko'rsatish kerakmi — dizayn qarori; Effektda ma'lumot olish darsida yuklanish holatini to'liq yozamiz.
4-mashq: Vazifalar qadami
refactor/effektlar branch'ida «Vazifalar qadami: tashqi tizimlar effektlarda» bo'limidagi to'rt effektni yozing, asosiy.ts va render.tsx ni o'chiring, commit qiling va PR oching. npm run check va npm run build toza bo'lsin.
Yechim
Tekshiruv ro'yxati:
grep -rn "addEventListener" src/App.tsx— har biriga juftremoveEventListenerbor.npm run dev— DevTools Network'da bitta ochilishda ikki GET, birinchisi(canceled): cleanup ishlayapti.- Ikki tab oching, birida vazifa belgilang — ikkinchisida darhol ko'rinadi; tabni yopsangiz, konsolda xato yo'q.
npm test— node 147, Vitest 22. Brauzer tekshiruvi — 159/159.
Commit — refactor: holat App state'ida, tashqi tizimlar — effektlar. PR'ni birlashtiring.
8. Real ishda
- Xotira oqishi (memory leak) — tozalanmagan tinglovchi va taymer React ilovalarida eng ko'p uchraydigan sekinlik sababi. Chrome DevTools'ning Memory paneli va
getEventListeners(window)(faqat DevTools konsolida) ularni topadi. AbortController—fetch,addEventListener({ signal }opsiyasi bilan), axios va ko'p kutubxonalar uni qabul qiladi. Bittacontroller.abort()bir nechta tinglovchi va so'rovni birdan to'xtatadi.- Data-fetching kutubxonalari (TanStack Query, SWR) ichida aynan shu cleanup va bekor qilish bor (TanStack Query I).
- Intervyu savollari: "useEffect cleanup qachon chaqiriladi?", "React'da race condition nima va qanday oldini olasiz?", "
ignorebayrog'i vaAbortControllerfarqi?".
Xulosa
- Effekt funksiyasi qaytargan funksiya — cleanup. React uni effekt qayta ishlashidan oldin va komponent olib tashlanganda chaqiradi.
- Effekt nima boshlasa — cleanup o'shani to'xtatadi:
clearInterval,removeEventListener, obunani bekor qilish,abort(). - Cleanup o'z effektining qiymatlarini ko'radi (closure) — juftlik: setup va cleanup.
- Race condition: kech yuborilgan so'rov oldin keladi. Davo —
ignorebayrog'i (javobni tashlaydi) yokiAbortController(so'rovni to'xtatadi). vazifalar: boshqa tablar, server, internet va URL — cleanup'li effektlar; 159/159, StrictMode dev'da birinchi GET bekor qilinadi.
Keyingi dars: Effekt hayot sikli va StrictMode'dagi ikki marta ishlash — "mount/unmount" emas, "boshla — to'xtat" deb o'ylash va StrictMode nega effektni dev'da ikki marta ishga tushiradi.
Manbalar
- React:
useEffect— "Connecting to an external system", "Fetching data with Effects" — react.dev/reference/react/useEffect - React: "Synchronizing with Effects" — "How to handle the Effect firing twice in development?" — react.dev/learn/synchronizing-with-effects
- MDN:
AbortController,AbortSignal— developer.mozilla.org/docs/Web/API/AbortController
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!