IlmHamroh
JavaScript Full-stack/18-qism. React chuqur2/42-dars24 daqiqa
Mundarija (30)

useDeferredValue: qiymatning kechiktirilgan nusxasi bilan sezgir qidiruv

Qisqacha: useDeferredValue(query) — query ning 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 va startTransition kerak emas, bitta const deferredQuery = useDeferredValue(query) yetadi. Shart bitta: qimmat komponent memo bilan o'ralgan bo'lsin. vazifalar kanoni shu yo'lni tanladi: 10 000 vazifada eng uzun javob 1 184 ms dan 208 ms ga tushdi.

Bu darsda

  • useDeferredValue ikki render qanday berishini konsolda ko'rasiz.
  • Qidiruvni bitta state bilan sezgir qilasiz va memo nega shart ekanini o'lchab bilasiz.
  • "Natija eskirgan" ko'rsatkichini (query !== deferredQuery, aria-busy) qo'shasiz.
  • initialValue argumentini va debounce'dan farqni tushuntira olasiz.
  • vazifalar kanonidagi 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:

jsx
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:

text
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:

  1. Shoshilinch renderda yangi value keldi. React kechiktirilgan nusxa uchun eski qiymatni qaytaradi.
  2. Shu render commit bo'lgach, React fon renderini rejalashtiradi — unda nusxa = yangi value.
  3. Fon renderi transition kabi: to'xtatiladi, tashlanadi, yangi value bilan 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: useDeferredValue ni ishlatdik, lekin ikkinchi renderda deferredQuery yangi 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:

jsx
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:

text
DishList chizilmoqda: 
ekranda: 
DishList chizilmoqda: cho
DishList chizilmoqda: choy
ekranda: choy

Ekranda:

text
Ko'k choy
Qora choy
Limonli choy
Zanjabil choy

Natija 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:

jsx
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:

text
DishList chizilmoqda: 
ekranda: 
DishList chizilmoqda: 
DishList chizilmoqda: cho
DishList chizilmoqda: 
DishList chizilmoqda: choy
ekranda: choy

Bo'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: memo faqat props haqiqatan o'sha bo'lsa yordam beradi. Ota har renderda yangi massiv yoki obyekt bersa (found={MENU.filter(…)}, style={{…}}, onPick={() => …}), Object.is uni "yangi" deb ko'radi — memo ishlamaydi. vazifalar da aynan shu tuzoq chiqdi — «Vazifalar qadami» bo'limida.

Tekshirib ko'ring: Jadvalning uchinchi qatori: memo bor, useDeferredValue yo'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.

jsx
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:

text
Yangilanmoqda…
Osh
Ko'k choy
Lag'mon
Qora choy
Manti
Limonli choy
Chuchvara
Zanjabil choy
Somsa

Bu — 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:

css
#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:

jsx
import { useDeferredValue } from "react";

function App() {
  const deferred = useDeferredValue("osh", "");
  console.log(`chizish: "${deferred}"`);
  return <p>Qidiruv: {deferred}</p>;
}

Konsolda:

text
chizish: ""
chizish: "osh"

Ekranda:

text
Qidiruv: osh

Nega 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:

ts
// 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 fetch yuboradi. Sardor uni useDeferredValue bilan "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):

10 000 vazifa, qidiruv: bitta harfga ishlovchi vaqti (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

tsx
  // 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) va sorov (kechiktirilgan nusxa) — «Qidiruv: bitta state» bo'limidagi query va deferredQuery. setQidiruv ning 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. useMemo siz har renderda korinadiganVazifalar(…) 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):

tsx
        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

tsx
// 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:

tsx
        <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):

tsx
  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:

  1. amallar — har renderda yangi, eng so'nggi ishlovchilar.
  2. useLayoutEffect har commit'dan keyin ularni songgiAmallar ref'iga yozadi (useRef I, useLayoutEffect). Ref'ga render paytida emas, commit'da yoziladi: tashlab yuborilgan render ref'ni buzmasligi uchun — concurrent rendering qoidasi.
  3. barqarorAmallar — useMemo(…, []): bir marta yasaladi va hech qachon o'zgarmaydi. Uning funksiyalari chaqirilganda ref'dagi eng so'nggi ishlovchini chaqiradi.
  4. <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

tsx
      <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:

tsx
    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:

js
  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:

text
perf: qidiruv — ro'yxat useDeferredValue bilan, memo va barqaror context

Commit 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:

jsx
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:

text
chizish: 0 true
chizish: 1 false
chizish: 1 true

filters 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 query props 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) va aria-busy bilan.

Ishora: «query !== deferredQuery» bo'limi.

jsx
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:

text
Ali
Malika
Jasur
Sardor
Madina
Yechim
jsx
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:

text
Qidirilmoqda…
Ali
Malika
Jasur
Sardor
Bobur
Madina

Eski 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?

jsx
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:

text
GuestList: 6 ta
GuestList: 6 ta
GuestList: 2 ta
Yechim
jsx
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:

text
GuestList: 6 ta
GuestList: 2 ta

Ekranda:

text
Malika
Madina

Muammo 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 — useDeferredValue ning 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: "useDeferredValue va debounce farqi?", "Nega memo siz ishlamaydi?", "useTransition dan 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 memo bilan o'ralgan va props'i barqaror bo'lsin: yangi massiv, obyekt yoki context qiymati memo ni aylanib o'tadi.
  • query !== deferredQuery — "natija eskirgan": xiralik (kechikish bilan) va aria-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.
  • vazifalar holat 02: 10 000 vazifada ishlovchi 1 024 → 62 ms, eng uzun javob 1 184 → 208 ms; useTransition bilan 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
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
useDeferredValue: qiymatning kechiktirilgan nusxasi bilan sezgir qidiruv — IlmHamroh