Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Kechiktirilgan qiymat
- 2.1 Ikki render — konsolda
- 2.2 Ichkarida nima bo'ladi
- 3. Qidiruv: bitta state
- 3.1 Og'ir ro'yxat bilan
- 3.2 memo — shart
- 4. "Natija eskirgan" ko'rsatkichi
- 4.1 query !== deferredQuery
- 4.2 Malika uchun: aria-busy
- 4.3 Miltillamasin
- 5. initialValue (React 19)
- 6. Debounce'dan farqi
- 7. useTransition yoki useDeferredValue
- 8. Vazifalar qadami: kechiktirilgan qidiruv (holat 02)
- 8.1 App.tsx: kechiktirilgan so'rov
- 8.2 VazifaRoyxati: memo
- 8.3 Barqaror context: "latest ref"
- 8.4 Ko'rsatkich: aria-busy va xiralik
- 8.5 Testlar, brauzer va o'lchov
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): qaysi vosita?
- 2-mashq (o'rta): "Qidirilmoqda…" ko'rsatkichi
- 3-mashq (qiyin): nega tezlashmadi?
- 4-mashq: vazifalar qadami
- 11. Real ishda
- Xulosa
- Manbalar
useDeferredValue: qiymatning kechiktirilgan nusxasi bilan sezgir qidiruv
Qisqacha:
useDeferredValue(query)—queryning kechiktirilgan nusxasi. Yangi harf kelganda React avval eski nusxa bilan tez chizadi (maydon darhol yangilanadi), keyin fonda yangi nusxa bilan qimmat qismni chizadi — xuddi transition kabi, to'xtatib bo'ladigan. Farqi: ikkinchi state vastartTransitionkerak emas, bittaconst deferredQuery = useDeferredValue(query)yetadi. Shart bitta: qimmat komponentmemobilan o'ralgan bo'lsin.vazifalarkanoni shu yo'lni tanladi: 10 000 vazifada eng uzun javob 1 184 ms dan 208 ms ga tushdi.
Bu darsda
useDeferredValueikki render qanday berishini konsolda ko'rasiz.- Qidiruvni bitta state bilan sezgir qilasiz va
memonega shart ekanini o'lchab bilasiz. - "Natija eskirgan" ko'rsatkichini (
query !== deferredQuery,aria-busy) qo'shasiz. initialValueargumentini va debounce'dan farqni tushuntira olasiz.vazifalarkanonidagi qidiruvni — barqaror context bilan — qatorma-qator o'qiysiz va commit qilasiz.
Oldin bilishingiz kerak: Concurrent rendering va useTransition, Context II: reducer bilan birga, performance va chegaralar, useRef I.
1. Nega bu kerak?
Oldingi darsda qidiruvni transition bilan tezlashtirdik. Lekin buning uchun ikki state kerak bo'ldi: text — maydon uchun, query — ro'yxat uchun. Har o'zgarishda ikkalasini birga yangilash kerak. vazifalar da qidiruv uch joyda o'zgaradi: maydon, Esc bilan tozalash va URL'dan tiklash. Uchala joy ham o'ram funksiya orqali o'tishi shart — bittasini unutsangiz, maydon va ro'yxat ajralib qoladi.
Ikkinchi muammo kattaroq: startTransition faqat setter qo'lingizda bo'lsa ishlaydi. Qidiruv matni ota komponentdan props bo'lib kelsa-chi? Yoki boshqa jamoa yozgan hook qaytarsa-chi? Setter sizda yo'q — transition qila olmaysiz.
Hayotiy o'xshatish: choyxonada tablo bor — "Hozir tayyorlanmoqda: …". Kassir har buyurtmani darhol qabul qiladi (bu — maydon). Tablo esa biroz kechikib yangilanadi: oshpaz yangi ro'yxatni tayyorlab bo'lgach. Kassir tabloga alohida buyruq bermaydi — tablo o'zi kassadagi ro'yxatga "ergashadi". useDeferredValue — shu tablo: u qiymatga o'zi ergashadi, lekin shoshilmaydi.
2. Kechiktirilgan qiymat
2.1 Ikki render — konsolda
useDeferredValue(value) — bitta qiymat oladi va uning "kechikkan" nusxasini qaytaradi. Eng kichik misol, og'ir narsa hali yo'q:
import { useDeferredValue, useState } from "react";
function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
console.log(
`chizish: query="${query}" deferred="${deferredQuery}"`,
);
return (
<input
placeholder="Qidiruv"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
);
}Konsolda:
chizish: query="" deferred=""
chizish: query="osh" deferred=""
chizish: query="osh" deferred="osh"Bitta yozuv — ikki render. Jadvalda:
| Render | query |
deferredQuery |
Turi |
|---|---|---|---|
| 1 | "" | "" | birinchi chizish |
| 2 | "osh" | "" — eski | shoshilinch: maydon yangilandi |
| 3 | "osh" | "osh" — yangi | fonda, transition kabi |
Ikkinchi renderda deferredQuery hali eski qiymat. React "avval muhim narsani chiz, kechiktirilganini keyin" deydi. Uchinchi render — fonda, xuddi useTransition dagi transition renderi: bo'laklarga bo'linadi va yangi harf kelsa tashlanadi.
Birinchi chizishda esa ikkalasi bir xil: kechiktirishga hali hech narsa yo'q, React value ni darhol qaytaradi.
2.2 Ichkarida nima bo'ladi
useDeferredValue yangi mexanizm emas — u oldingi darsdagi transition'ning qulay shakli. Taxminan shunday ishlaydi:
- Shoshilinch renderda yangi
valuekeldi. React kechiktirilgan nusxa uchun eski qiymatni qaytaradi. - Shu render commit bo'lgach, React fon renderini rejalashtiradi — unda nusxa = yangi
value. - Fon renderi transition kabi: to'xtatiladi, tashlanadi, yangi
valuebilan qaytadan boshlanadi.
sequenceDiagram
participant U as Foydalanuvchi
participant R as React
participant E as Ekran
U->>R: "c" yozdi
R->>E: shoshilinch: maydon "c", ro'yxat eski
R->>R: fon: ro'yxat "c" bilan…
U->>R: "ch" yozdi
R->>R: fon renderi "c" tashlandi
R->>E: shoshilinch: maydon "ch", ro'yxat eski
R->>R: fon: ro'yxat "ch" bilan
R->>E: commit: ro'yxat "ch"Diagrammadagi asosiy narsa: ekranga faqat ikki xil narsa chiqadi — darhol yangilangan maydon va oxirgi harf bo'yicha ro'yxat. Oraliq "c" ro'yxati hech qachon chizib tugatilmadi.
Tekshirib ko'ring:
useDeferredValueni ishlatdik, lekin ikkinchi renderdadeferredQueryyangi qiymat bilan keldi. Bu qachon bo'lishi mumkin?
Javob
Birinchi chizishda (mount) — yuqorida ko'rdik: kechiktirishga hech narsa yo'q. Yana bir holat — yangilanishning o'zi allaqachon transition bo'lsa (masalan, startTransition ichida o'zgargan bo'lsa). Unda React qo'shimcha fon renderi yaratmaydi: butun render fonda, kechiktirishning ma'nosi yo'q. React hujjati shunday yozadi.
3. Qidiruv: bitta state
3.1 Og'ir ro'yxat bilan
Oldingi darsdagi choyxona menyusi. Har taom kartasi slowWork() bilan ataylab sekin: bizning kompyuterda (Intel Core i5-12500H, Node 24) bitta karta 50–90 ms. Endi text va startTransition yo'q — faqat query va uning kechiktirilgan nusxasi:
import { memo, useDeferredValue, useEffect, useState } from "react";
const MENU = ["Osh", "Ko'k choy", "Lag'mon", "Qora choy", "Manti",
"Limonli choy", "Chuchvara", "Zanjabil choy", "Somsa"];
function slowWork() {
let x = 0;
for (let i = 0; i < 20_000_000; i++) x += i % 7;
return x;
}
function Dish({ name }) {
slowWork(); // ataylab sekin
return <li>{name}</li>;
}
const DishList = memo(function DishList({ query }) {
console.log("DishList chizilmoqda:", query);
useEffect(() => console.log("ekranda:", query), [query]);
const found = MENU.filter((d) => d.toLowerCase().includes(query));
return <ul>{found.map((d) => <Dish key={d} name={d} />)}</ul>;
});
function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
return (
<div>
<input
placeholder="Qidiruv"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<DishList query={deferredQuery} />
</div>
);
}Konsolda:
DishList chizilmoqda:
ekranda:
DishList chizilmoqda: cho
DishList chizilmoqda: choy
ekranda: choyEkranda:
Ko'k choy
Qora choy
Limonli choy
Zanjabil choyNatija oldingi darsdagi useTransition varianti bilan harfma-harf bir xil: "cho" bo'yicha render boshlandi va tashlandi, ekranga faqat "choy" chiqdi. Kod esa soddaroq: bitta state, oddiy onChange. Maydonga query, og'ir ro'yxatga deferredQuery beriladi — butun g'oya shu ikki qatorda.
yoz(…) qadamlari orasida tekshiruvchi 60 ms kutadi — tez yozayotgan odam kabi. «Ishga tushir» bilan o'zingiz yozib ko'ring: harflar darhol chiqadi, ro'yxat esa siz to'xtagach keladi.
3.2 memo — shart
Oldingi darsda ko'rdik: shoshilinch render og'ir komponentni baribir chizsa, transition foyda bermaydi. useDeferredValue da ham xuddi shunday. Quyidagi blok — oldingisining davomi, faqat DishList dan memo olib tashlangan. Konsolda bo'sh query bilan chiqqan qatorlarga qarang:
function DishList({ query }) {
console.log("DishList chizilmoqda:", query);
useEffect(() => console.log("ekranda:", query), [query]);
const found = MENU.filter((d) => d.toLowerCase().includes(query));
return <ul>{found.map((d) => <Dish key={d} name={d} />)}</ul>;
}Konsolda:
DishList chizilmoqda:
ekranda:
DishList chizilmoqda:
DishList chizilmoqda: cho
DishList chizilmoqda:
DishList chizilmoqda: choy
ekranda: choyBo'sh query bilan ikki qo'shimcha qator — har harfning shoshilinch renderi. App qayta chizildi, u esa DishList ni ham chizdi — eski deferredQuery bilan, lekin to'liq: to'qqizta sekin karta, to'xtatib bo'lmaydigan renderda. Maydon yana qotdi.
memo — props avvalgisi bilan bir xil bo'lsa (Object.is), komponentni qayta chizmaydigan o'ram (memo, useMemo, useCallback darsida to'liq). Shoshilinch renderda deferredQuery o'zgarmagan — memo li DishList darhol o'tkazib yuboriladi. Ikkalasi juftlikda ishlaydi:
memo |
useDeferredValue |
Shoshilinch renderda ro'yxat | Maydon |
|---|---|---|---|
| yo'q | yo'q | yangi so'rov bilan, to'liq | qotadi |
| yo'q | bor | eski so'rov bilan, to'liq | qotadi |
| bor | yo'q | yangi so'rov bilan, to'liq | qotadi |
| bor | bor | chizilmaydi | darhol |
Diqqat:
memofaqat props haqiqatan o'sha bo'lsa yordam beradi. Ota har renderda yangi massiv yoki obyekt bersa (found={MENU.filter(…)},style={{…}},onPick={() => …}),Object.isuni "yangi" deb ko'radi —memoishlamaydi.vazifalarda aynan shu tuzoq chiqdi — «Vazifalar qadami» bo'limida.
Tekshirib ko'ring: Jadvalning uchinchi qatori:
memobor,useDeferredValueyo'q. Nega ro'yxat baribir har harfda to'liq chiziladi?
Javob
Chunki DishList ga to'g'ridan-to'g'ri query beriladi va u har harfda o'zgaradi. Shuning uchun memo props o'zgarganini ko'radi va komponentni chizadi — shoshilinch renderda, to'xtatib bo'lmaydigan. memo "o'zgarmagan bo'lsa, chizma" deydi, useDeferredValue esa shoshilinch renderda "o'zgarmagan" qiymat beradi. Bittasi yolg'iz yetmaydi.
4. "Natija eskirgan" ko'rsatkichi
4.1 query !== deferredQuery
Fon renderi tugaguncha ekranda eski ro'yxat turadi. Foydalanuvchi "choy" deb yozdi, ro'yxatda esa hali butun menyu — u chalkashishi mumkin. Buni bitta taqqoslash bilan bilamiz: query va deferredQuery farq qilsa, ekrandagi ro'yxat eskirgan.
import { memo, useDeferredValue, useState } from "react";
const MENU = ["Osh", "Ko'k choy", "Lag'mon", "Qora choy", "Manti",
"Limonli choy", "Chuchvara", "Zanjabil choy", "Somsa"];
function slowWork() {
let x = 0;
for (let i = 0; i < 20_000_000; i++) x += i % 7;
return x;
}
function Dish({ name }) {
slowWork();
return <li>{name}</li>;
}
const DishList = memo(function DishList({ query }) {
const found = MENU.filter((d) => d.toLowerCase().includes(query));
return <ul>{found.map((d) => <Dish key={d} name={d} />)}</ul>;
});
function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery; // ekrandagi natija eski
return (
<div>
<input
placeholder="Qidiruv"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
{isStale && <p>Yangilanmoqda…</p>}
<div
aria-busy={isStale ? true : undefined}
style={{ opacity: isStale ? 0.5 : 1 }}
>
<DishList query={deferredQuery} />
</div>
</div>
);
}"choy" yozilgandan bir lahza keyin (tekshiruvchi 60 ms kutadi) ekranda:
Yangilanmoqda…
Osh
Ko'k choy
Lag'mon
Qora choy
Manti
Limonli choy
Chuchvara
Zanjabil choy
SomsaBu — fon renderining o'rtasi: hali butun menyu, lekin xira va ustida "Yangilanmoqda…". Fon renderi tugagach, deferredQuery "choy" bo'ladi, isStale — false, ro'yxat to'rtta choyga qisqaradi va yana aniq ko'rinadi. Hammasi bitta commit'da.
Oldingi darsdagi isPending bilan solishtiring: u ham "fonda ish bor" deydi. isStale ning farqi — uni React bermaydi, biz ikki qiymatni o'zimiz solishtiramiz.
4.2 Malika uchun: aria-busy
Malika ekran o'quvchi bilan ishlaydi — xiralikni ko'rmaydi. aria-busy="true" — ekran o'quvchiga "bu hudud hozir yangilanmoqda, tugaguncha o'qima" degan atribut (ARIA asoslari). Yangilanish tugagach atribut olib tashlanadi. aria-busy={isStale ? true : undefined} — false o'rniga undefined: React atributni umuman yozmaydi, DOM toza qoladi.
4.3 Miltillamasin
Tez kompyuterda fon renderi 20–30 ms da tugaydi. Xiralik har harfda "yonib-o'chib" turadi — bu ham bezovta qiladi. Yechim CSS'da: xiralikni darhol emas, biroz kechikib ko'rsatish. vazifalar kanonidagi qoida:
#royxat.eskirgan {
opacity: 0.6;
transition: opacity 0.15s 0.2s;
}
@media (prefers-reduced-motion: reduce) {
#royxat.eskirgan { transition: none; }
}transition: opacity 0.15s 0.2s — xiralik 0.2 s kechikish bilan, 0.15 s davomida keladi (CSS transition). Javob 200 ms dan tez kelsa, xiralik umuman ko'rinmaydi. Harakatni kamaytirishni so'ragan foydalanuvchida esa animatsiyasiz.
5. initialValue (React 19)
React 19 da ikkinchi argument qo'shildi: useDeferredValue(value, initialValue). Birinchi chizishda hook value emas, initialValue ni qaytaradi, keyin fonda value bilan qayta chizadi:
import { useDeferredValue } from "react";
function App() {
const deferred = useDeferredValue("osh", "");
console.log(`chizish: "${deferred}"`);
return <p>Qidiruv: {deferred}</p>;
}Konsolda:
chizish: ""
chizish: "osh"Ekranda:
Qidiruv: oshNega kerak? Sahifa birinchi ochilganda ham og'ir qism bor bo'lishi mumkin — masalan, URL'dan ?q=osh bilan kelgan qidiruv. Agar initialValue bo'lmasa, birinchi chizishning o'zi og'ir va to'xtatib bo'lmaydi. Bu argument bilan esa React avval yengil variantni ("" — bo'sh ro'yxat yoki skelet) tez chiqaradi, og'ir natijani esa fonda tayyorlaydi. vazifalar da bunga ehtiyoj bo'lmadi: ilova bo'sh qidiruv bilan ochiladi.
6. Debounce'dan farqi
Debounce bilan tanishsiz — vazifalar da u bor: kechiktir() (taymer.ts). Yangi vazifa qoralamasi har harfda emas, yozish to'xtagach 400 ms o'tib localStorage ga saqlanadi:
// localStorage sinxron — har harfda yozish kiritishni sekinlatadi
const QORALAMA_KUTISH_MS = 400;
const qoralamaniKechiktir = kechiktir(
qoralamaniSaqla,
QORALAMA_KUTISH_MS,
);Ikkalasi ham "keyinroq" qiladi. Farqi qayerda?
Debounce (kechiktir) |
useDeferredValue |
|
|---|---|---|
| Kutish | belgilangan: doim 400 ms | yo'q: kompyuter bo'sh bo'lishi bilan |
| Tez kompyuterda | baribir 400 ms kutadi | deyarli darhol |
| Ish boshlangach | to'xtatib bo'lmaydi | to'xtatiladi va tashlanadi |
| Nimani kamaytiradi | chaqiruvlar sonini | faqat render bosqichini |
Oxirgi qator — tanlov qoidasi. Serverga so'rov, localStorage ga yozish, analitika — bular React render emas, ularni useDeferredValue sekinlashtirmaydi va kamaytirmaydi. Ularga debounce (yoki throttle) kerak. Og'ir chizish uchun esa useDeferredValue yaxshiroq: tez qurilmada kutish yo'q, sekinida esa React o'zi moslashadi. Ikkalasi bitta ilovada birga yashaydi — vazifalar dagidek. Batafsil — Og'ir ishni UI'dan ajratish.
Tekshirib ko'ring: Qidiruv har harfda serverga
fetchyuboradi. Sardor uniuseDeferredValuebilan "kamaytirmoqchi". Bo'ladimi?
Javob
Yo'q. fetch render emas — u effektda yoki ishlovchida chaqiriladi. Effekt deferredQuery ga bog'langan bo'lsa ham, tez yozilgan har harf baribir commit bo'lishi mumkin — so'rovlar soni kafolatli kamaymaydi. So'rovlar sonini kamaytirish — debounce'ning ishi, poyga holati esa — AbortController (useEffect III).
7. useTransition yoki useDeferredValue
Ikkala hook bitta mexanizmga tayanadi. Qaysini qachon:
| Vaziyat | Tanlov |
|---|---|
| Setter qo'lingizda, yangilanish ishlovchida | useTransition |
| Qiymat props yoki boshqa hookdan keladi | useDeferredValue |
Async ish (serverga saqlash) va isPending kerak |
useTransition (Action) |
| Bitta qiymat — tez qism va qimmat qism | useDeferredValue |
vazifalar da ikkalasi ham o'lchandi (10 000 vazifa, "vazifa 12" haqiqiy klaviatura bilan, Chrome 154, 5 marta mediana):
- v7: sinxroneng uzun javob 1184 ms1 024 ms
- useTransition (ikki state)eng uzun javob 168 ms60 ms
- useDeferredValue (kanon)eng uzun javob 208 ms62 ms
Manba: O'lchandi: vazifalar v7 va holat 02, olchov/qidiruv-tezligi.mjs, Chrome 154 headless, Intel Core i5-12500H, 5 marta mediana, 2026-10-13
Ishlovchi — 60 va 62 ms, eng uzun javob — 168 va 208 ms. Besh o'lchovning o'zida tarqoqlik bundan katta: transition 152–248 ms, deferred 160–464 ms. Ya'ni farq shovqin ichida — tezlik bo'yicha g'olib yo'q. Kanon soddasini tanladi: bitta state, uchta chaqiruv joyi o'zgarmaydi.
8. Vazifalar qadami: kechiktirilgan qidiruv (holat 02)
perf/qidiruv-kechiktirish branch'ida davom etamiz. Oldingi darsdagi useTransition varianti — o'lchov uchun edi. Kanon: useDeferredValue, memo va barqaror context. Diff: 5 fayl, +137 −42 (App.tsx, VazifaRoyxati.tsx, QidiruvMaydoni.tsx, vazifalar.css va test).
8.1 App.tsx: kechiktirilgan so'rov
// 18/#02 (useDeferredValue): maydon (qidiruv) — shoshilinch,
// har harfda darhol; ro'yxat — KECHIKTIRILGAN so'rov bilan.
// React avval maydonni eski ro'yxat bilan chizadi, keyin fonda
// yangi ro'yxatni; yangi harf kelsa — fondagi ishni tashlab,
// qaytadan.
const sorov = useDeferredValue(qidiruv);
const eskirgan = sorov !== qidiruv; // ekranda hali eski natija
// useMemo: kechiktirish faqat qimmat qism QAYTA CHIZILMASA foyda
// beradi — shoshilinch renderda massiv o'sha bo'lsin (memo'li
// VazifaRoyxati uni solishtiradi)
const korinadigan = useMemo(
() => korinadiganVazifalar(vazifalar, filtr, saralash, sorov),
[vazifalar, filtr, saralash, sorov],
);Kanon izohlari telefonga sig'ishi uchun qayta bo'lindi, birinchi izohning o'lchov raqamlari bilan davomi esa qisqartirildi (ular «useTransition yoki useDeferredValue» bo'limida). Kod qatorlari — aynan. Ikki narsa yangi:
qidiruv(maydon) vasorov(kechiktirilgan nusxa) — «Qidiruv: bitta state» bo'limidagiqueryvadeferredQuery.setQidiruvning uchala chaqiruv joyi o'zgarmadi.useMemo— hisob natijasini renderlar orasida saqlab turadigan hook. Massivdagi qiymatlar o'zgarmasa, funksiya qayta chaqirilmaydi va o'sha massiv qaytadi. U ham memo, useMemo, useCallback darsida to'liq. Bu yerda u hisobni tejash uchun emas, massiv havolasi uchun kerak.useMemosiz har renderdakorinadiganVazifalar(…)yangi massiv qaytarardi — «memo— shart» bo'limidagi "Diqqat" tuzog'i.
Ro'yxat va undagi matn endi sorov dan chiziladi. "Topildi" soni ham — QidiruvMaydoni endi topildi ni tayyor holda oladi (null — qidiruv bo'sh):
topildi={normalla(sorov) === "" ? null : korinadigan.length}Nega maydondagi qidiruv emas? Malikaning ekran o'quvchisi "1 ta topildi" deb e'lon qiladi (role="status", Mini-ilova). Son ekrandagi ro'yxatniki bo'lishi kerak. Aks holda u "1 ta" deydi, ro'yxatda esa hali 10 000 qator turadi.
8.2 VazifaRoyxati: memo
// memo (18/#02): props o'sha bo'lsa (Object.is), qayta
// chizilmaydi. useDeferredValue shusiz ishlamaydi: qidiruvning
// shoshilinch renderida App qayta chiziladi, lekin vazifalar
// (useMemo) va sorov (kechiktirilgan) — eski, demak 10 000 qator
// tegilmaydi. Batafsil o'lchov — 18/#18–#20
export const VazifaRoyxati = memo(function VazifaRoyxati({
vazifalar,
sorov,
tahrirId,
}: Props) {Uchala props shoshilinch renderda o'sha: vazifalar — useMemo dan o'sha massiv, sorov — eski qiymat, tahrirId — o'zgarmagan. Izoh qatorlari qayta bo'lingan, matni o'sha.
8.3 Barqaror context: "latest ref"
Bu ikkisi ham yetmadi. Ro'yxat ichida har VazifaQatori qator hodisalarini context'dan oladi (Context I). v7 da context qiymati JSX ichida yasalardi:
<QatorAmallariKonteksti
value={{
onAlmashtir: katakniAlmashtir,
onOchir: ochirishniSora,
…
}}
>Har renderda yangi obyekt. Context qiymati o'zgarsa, uni o'qiydigan har komponent qayta chiziladi — memo ham to'xtata olmaydi (Context II darsidagi chegara). Har harfda 10 000 qator yana chiziladi.
Obyektni shunchaki useMemo ga o'rash ham bo'lmaydi: ishlovchilar (katakniAlmashtir va boshqalar) har renderda yangi, chunki ular eng so'nggi state'ni ko'rishi kerak (snapshot). Kanondagi yechim — "latest ref" naqshi (eng so'nggisini ref'da saqlash):
const songgiAmallar = useRef<QatorHodisalari | null>(null);
const amallar: QatorHodisalari = {
onAlmashtir: katakniAlmashtir,
onOchir: ochirishniSora,
onTahrirla: tahrirniBoshla,
onSaqla: tahrirniSaqla,
onBekor: tahrirniBekorQil,
};
useLayoutEffect(() => {
songgiAmallar.current = amallar;
});
const barqarorAmallar = useMemo<QatorAmallari>(() => {
const ol = (): QatorHodisalari => {
if (songgiAmallar.current === null) {
throw new Error("Qator hodisasi commit'dan oldin chaqirildi");
}
return songgiAmallar.current;
};
return {
onAlmashtir: (id) => ol().onAlmashtir(id),
onOchir: (id) => ol().onOchir(id),
onTahrirla: (id) => ol().onTahrirla(id),
onSaqla: (matn) => ol().onSaqla(matn),
onBekor: () => ol().onBekor(),
// Render paytida chaqiriladi (ref callback yasaydi) — ref'ga
// tegmaydi, faqat qaytargan callback commit'da Map'ga yozadi
tugmaRef: (id, amal) => tugmaRef(tugmalar, id, amal),
};
}, []);Qadamma-qadam:
amallar— har renderda yangi, eng so'nggi ishlovchilar.useLayoutEffecthar commit'dan keyin ularnisonggiAmallarref'iga yozadi (useRef I, useLayoutEffect). Ref'ga render paytida emas, commit'da yoziladi: tashlab yuborilgan render ref'ni buzmasligi uchun — concurrent rendering qoidasi.barqarorAmallar—useMemo(…, []): bir marta yasaladi va hech qachon o'zgarmaydi. Uning funksiyalari chaqirilganda ref'dagi eng so'nggi ishlovchini chaqiradi.<QatorAmallariKonteksti value={barqarorAmallar}>— context qiymati doim o'sha obyekt, iste'molchilar qayta chizilmaydi.
Bu qisqartirmasdan, kanondagidek. Ref callback'ni yasaydigan tugmaRef ham komponentdan tashqariga chiqdi — u faqat tugmalar Map'i bilan ishlaydi, state o'qimaydi. React jamoasi shu naqsh uchun useEffectEvent ni ham taklif qiladi, lekin u faqat effektlar ichida chaqirishga mo'ljallangan (useEffectEvent) — hodisa ishlovchisi sifatida bolaga berish uchun emas.
8.4 Ko'rsatkich: aria-busy va xiralik
<ul
ref={royxatRef}
id="royxat"
aria-labelledby={sarlavhaId}
aria-busy={eskirgan ? true : undefined}
className={eskirgan ? "eskirgan" : undefined}
>CSS — «Miltillamasin» bo'limidagi qoida (vazifalar.css ga +9 qator).
8.5 Testlar, brauzer va o'lchov
Vitest'ga yangi test qo'shildi: maydonga "kitob" yozilganda qiymat darhol maydonda, ro'yxat esa waitFor bilan — oxirida bitta qator, "1 ta topildi" va aria-busy yo'q:
fireEvent.change(maydon, { target: { value: "kitob" } });
expect(maydon).toHaveProperty("value", "kitob");
await waitFor(() =>
expect(screen.getAllByRole("listitem")).toHaveLength(1),
);waitFor — shart qanoatlanguncha qayta tekshiradi: fon renderi qachon tugashini test oldindan bilmaydi.
Brauzer tekshiruvida bitta haqiqiy o'zgarish kerak bo'ldi (e2e.mjs, o'zgarish 4). Ssenariy qidiruvga yozib, ro'yxatni shu zahoti o'qirdi. v7 da bu to'g'ri edi — ro'yxat sinxron chizilardi. Endi ro'yxat kechikib keladi va ssenariy eski natijani ko'rdi: "zzz" dan keyin 1 qator qoldi, Esc dan keyin 0 qator. Tuzatish — kutish, aynan aria-busy bo'yicha:
await p.waitForFunction(() => !document.getElementById("royxat").hasAttribute("aria-busy"), { timeout: 10000, polling: 20 });Kutilgan qiymatlar o'zgarmadi — faqat "qachon o'qish". aria-busy shu yerda ham foyda berdi: test "ro'yxat tayyor"ni taxmin emas, atribut bo'yicha biladi.
Natija: build, lint, format:check, tip toza; testlar — node 156, Vitest 30; brauzer — 159/159. O'lchov — «useTransition yoki useDeferredValue» bo'limidagi grafik: eng uzun javob 1 184 → 208 ms, ishlovchi 1 024 → 62 ms. Qolgan ~200 ms — App dagi boshqa hisoblar (teglar, statistika) va 10 000 qatorli commit. Ular memo va virtualizatsiya darslarida.
Commit:
perf: qidiruv — ro'yxat useDeferredValue bilan, memo va barqaror contextCommit tanasida (body) — to'rt qator: sorov = useDeferredValue(qidiruv); korinadigan — useMemo; memo(VazifaRoyxati); qator hodisalari context'i — barqaror obyekt ("latest ref"), aria-busy va xiralik.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
useDeferredValue bor, maydon baribir qotadi |
og'ir komponent memo siz |
memo bilan o'rang |
memo bor, baribir qotadi |
props har renderda yangi (massiv, obyekt, funksiya) | useMemo yoki qiymatni bolaning ichida hisoblang |
memo va props to'g'ri, baribir qotadi |
context qiymati har renderda yangi | barqaror context ("latest ref") |
| ortiqcha fon renderlari | useDeferredValue ga render ichida yasalgan obyekt |
primitiv yoki tashqarida yasalgan qiymat |
| test eski ro'yxatni ko'radi | ro'yxat kechikib chiziladi | waitFor yoki aria-busy ni kutish |
| so'rovlar soni kamaymadi | useDeferredValue render bo'lmagan ishni kamaytirmaydi |
debounce |
To'rtinchi qator haqida — o'zingiz ko'ring:
import { useDeferredValue, useState } from "react";
function App() {
const [count, setCount] = useState(0);
const filters = { dish: "osh" }; // ❌ har renderda yangi obyekt
const deferred = useDeferredValue(filters);
console.log("chizish:", count, deferred === filters);
return <button onClick={() => setCount(count + 1)}>+1</button>;
}Konsolda:
chizish: 0 true
chizish: 1 false
chizish: 1 truefilters ning mazmuni hech qachon o'zgarmaydi. Lekin har render yangi obyekt yasaydi, React esa uni Object.is bilan solishtiradi: "yangi qiymat!" Shuning uchun sanagichning har bosishi ikki render beradi — biri ortiqcha. Davosi: useDeferredValue("osh") (primitiv) yoki obyektni komponentdan tashqarida e'lon qilish.
10. Mashqlar
Mashqlar kurs/mashqlar/18/02-usedeferredvalue/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): qaysi vosita?
- Har harfda serverga so'rov ketmasin, yozish to'xtagach bitta ketsin:
- 5 000 qatorli jadval qidiruv bilan qayta chizilsin, maydon qotmasin:
- Qidiruv matni ota komponentdan
queryprops bo'lib keladi, natija og'ir: - "Saqlash" bosilganda serverga yozish va kutish vaqtida tugmani o'chirish:
Yechim
1 — so'rovlar sonini kamaytirish: debounce («Debounce'dan farqi» bo'limi). 2 va 3 — og'ir render. Uchinchisida setter umuman sizda yo'q, shuning uchun faqat useDeferredValue mumkin. 4 — async Action va isPending: useTransition (Concurrent rendering va useTransition).
2-mashq (o'rta): "Qidirilmoqda…" ko'rsatkichi
«Bahor» mehmonlar ro'yxati. Qidiruv useDeferredValue bilan ishlaydi, lekin eski natija hech qanday belgisiz turadi. Qo'shing:
- ro'yxat eskirgan paytda uning ustida
Qidirilmoqda…matni; - o'sha paytda ro'yxat xira (
opacity: 0.5) vaaria-busybilan.
Ishora: «query !== deferredQuery» bo'limi.
import { memo, useDeferredValue, useState } from "react";
const NAMES = ["Ali", "Malika", "Jasur", "Sardor", "Bobur", "Madina"];
function slowWork() {
let x = 0;
for (let i = 0; i < 20_000_000; i++) x += i % 7;
return x;
}
function Guest({ name }) {
slowWork();
return <li>{name}</li>;
}
const GuestList = memo(function GuestList({ query }) {
const found = NAMES.filter((g) => g.toLowerCase().includes(query));
return <ul>{found.map((g) => <Guest key={g} name={g} />)}</ul>;
});
function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
return (
<div>
<input
placeholder="Mehmon"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<GuestList query={deferredQuery} />
</div>
);
}Ekranda:
Ali
Malika
Jasur
Sardor
MadinaYechim
import { memo, useDeferredValue, useState } from "react";
const NAMES = ["Ali", "Malika", "Jasur", "Sardor", "Bobur", "Madina"];
function slowWork() {
let x = 0;
for (let i = 0; i < 20_000_000; i++) x += i % 7;
return x;
}
function Guest({ name }) {
slowWork();
return <li>{name}</li>;
}
const GuestList = memo(function GuestList({ query }) {
const found = NAMES.filter((g) => g.toLowerCase().includes(query));
return <ul>{found.map((g) => <Guest key={g} name={g} />)}</ul>;
});
function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
return (
<div>
<input
placeholder="Mehmon"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
{isStale && <p>Qidirilmoqda…</p>}
<div
aria-busy={isStale ? true : undefined}
style={{ opacity: isStale ? 0.5 : 1 }}
>
<GuestList query={deferredQuery} />
</div>
</div>
);
}"a" yozilgandan bir lahza keyin (fon renderi hali tugamagan) ekranda:
Qidirilmoqda…
Ali
Malika
Jasur
Sardor
Bobur
MadinaEski ro'yxat — oltita mehmon — xira va ustida "Qidirilmoqda…". Fon renderi tugagach ikkalasi yo'qoladi, ro'yxatda «a» harfli beshta ism qoladi (Bobur chiqib ketadi). Bu harakat qatorida kut yo'q — ekran fon renderining o'rtasida o'qiladi.
3-mashq (qiyin): nega tezlashmadi?
Sardor mehmonlar ro'yxatiga useDeferredValue va memo qo'shdi, lekin maydon baribir qotadi. Konsolga qarang: har harfda GuestList ikki marta chiziladi. Kodni shunday o'zgartiringki, harfdan keyin GuestList faqat bir marta — fonda chizilsin. Konsolda GuestList: 6 ta va GuestList: 2 ta qolsin.
Ishora: «memo — shart» bo'limidagi "Diqqat" — found har renderda qanday massiv?
import { memo, useDeferredValue, useState } from "react";
const NAMES = ["Ali", "Malika", "Jasur", "Sardor", "Bobur", "Madina"];
function slowWork() {
let x = 0;
for (let i = 0; i < 20_000_000; i++) x += i % 7;
return x;
}
function Guest({ name }) {
slowWork();
return <li>{name}</li>;
}
const GuestList = memo(function GuestList({ guests }) {
console.log(`GuestList: ${guests.length} ta`);
return <ul>{guests.map((g) => <Guest key={g} name={g} />)}</ul>;
});
function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const found = NAMES.filter((g) =>
g.toLowerCase().includes(deferredQuery),
);
return (
<div>
<input
placeholder="Mehmon"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<GuestList guests={found} />
</div>
);
}Konsolda:
GuestList: 6 ta
GuestList: 6 ta
GuestList: 2 taYechim
import { memo, useDeferredValue, useState } from "react";
const NAMES = ["Ali", "Malika", "Jasur", "Sardor", "Bobur", "Madina"];
function slowWork() {
let x = 0;
for (let i = 0; i < 20_000_000; i++) x += i % 7;
return x;
}
function Guest({ name }) {
slowWork();
return <li>{name}</li>;
}
// ✅ props — satr: shoshilinch renderda o'sha qiymat
const GuestList = memo(function GuestList({ query }) {
const found = NAMES.filter((g) => g.toLowerCase().includes(query));
console.log(`GuestList: ${found.length} ta`);
return <ul>{found.map((g) => <Guest key={g} name={g} />)}</ul>;
});
function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
return (
<div>
<input
placeholder="Mehmon"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<GuestList query={deferredQuery} />
</div>
);
}Konsolda:
GuestList: 6 ta
GuestList: 2 taEkranda:
Malika
MadinaMuammo found da edi. NAMES.filter(…) har renderda yangi massiv qaytaradi — shoshilinch renderda ham, garchi deferredQuery eski bo'lsa-da. memo uni Object.is bilan solishtirdi: "yangi massiv" — va oltita sekin mehmonni qayta chizdi. Filtrni GuestList ichiga ko'chirdik, props endi satr: shoshilinch renderda u o'sha "", memo chizishni o'tkazib yuboradi. Ikkinchi yo'l — found ni useMemo(() => …, [deferredQuery]) bilan saqlash (memo, useMemo, useCallback) — vazifalar kanonida aynan shunday.
4-mashq: vazifalar qadami
perf/qidiruv-kechiktirish branch'ida oldingi darsdagi variantni bekor qiling va «Vazifalar qadami» dagi to'rt o'zgarishni kiriting: useDeferredValue + useMemo, memo(VazifaRoyxati), barqaror context, aria-busy va .eskirgan. QidiruvMaydoni ga topildi ni number | null qilib bering. Vitest'ga «Testlar, brauzer va o'lchov» dagi testni qo'shing. npm test, npm run lint, npm run tip toza bo'lsin; commit va PR.
Yechim
Tekshirish tartibi uch qadam. Birinchi: npm test — node 156, Vitest 30 (yangi test bilan). Ikkinchi: build qiling va brauzerda 10 000 vazifa bilan qidiruvga tez yozing — harflar darhol chiqishi kerak. Uchinchi: React DevTools → Profiler'da bitta harfni yozib oling. Shoshilinch renderda VazifaRoyxati kulrang ("did not render") bo'lishi kerak (React DevTools). Agar u chizilgan bo'lsa — props'dan qaysi biri yangi ekanini Profiler'ning "Why did this render?" qatori aytadi: ko'pincha vazifalar (useMemo unutilgan) yoki context ("• Context changed"). Commit xabari — «Testlar, brauzer va o'lchov» bo'limida.
11. Real ishda
- Qidiruv, filtr, avtoto'ldirish katta ro'yxat ustida —
useDeferredValuening asosiy joyi: CRM, admin panel, mahsulotlar katalogi. - Diagramma va xarita: surgich (slider) siljiganda og'ir grafik kechiktirilgan qiymat bilan chiziladi — surgich esa silliq.
- Suspense bilan: kechiktirilgan qiymat yangi ma'lumot kutayotganda eski natijani ko'rsatib turadi, "Yuklanmoqda" bilan almashtirmaydi — keyingi darsda (Suspense).
- Intervyu savollari: "
useDeferredValueva debounce farqi?", "Negamemosiz ishlamaydi?", "useTransitiondan qachon afzal?", "Eskirgan natijani foydalanuvchiga qanday ko'rsatasiz?".
Xulosa
useDeferredValue(value)— kechiktirilgan nusxa: shoshilinch renderda eski qiymat, keyin fonda (to'xtatib bo'ladigan) yangi qiymat bilan render.- Ishlashi uchun qimmat komponent
memobilan o'ralgan va props'i barqaror bo'lsin: yangi massiv, obyekt yoki context qiymatimemoni aylanib o'tadi. query !== deferredQuery— "natija eskirgan": xiralik (kechikish bilan) vaaria-busy.- React 19:
useDeferredValue(value, initialValue)— birinchi chizishda yengil variant. - Debounce chaqiruvlar sonini kamaytiradi (so'rov, yozish),
useDeferredValue— og'ir renderni to'xtatib bo'ladigan qiladi; ikkalasi birga yashaydi. vazifalarholat 02: 10 000 vazifada ishlovchi 1 024 → 62 ms, eng uzun javob 1 184 → 208 ms;useTransitionbilan farq shovqin ichida, kanon — soddasi.
Keyingi dars: Suspense: kutish holatini deklarativ ko'rsatish — tayyor bo'lmagan qism o'rniga fallback va chegaralarning joyi UX'ni qanday belgilashi.
Manbalar
- React:
useDeferredValue— react.dev/reference/react/useDeferredValue - React:
memo— react.dev/reference/react/memo - React 19 e'loni: "useDeferredValue initial value" (2024-12-05) — react.dev/blog/2024/12/05/react-19
- MDN:
aria-busy— developer.mozilla.org/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-busy - Kanon:
sinov/18/QADAMLAR.md(#01–#02),natijalar/02-qidiruv.txt
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!