IlmHamroh
JavaScript Full-stack/17-qism. React asoslari33/45-dars18 daqiqa
Mundarija (30)

useRef I: qayta chizish chaqirmaydigan xotira — taymer id, hisoblagich, oldingi qiymat

Qisqacha: useRef(boshlang'ich) — komponentga { current: … } qutisini beradi. Quti har chizishda o'sha obyekt, ref.current ni istagancha o'zgartirsa bo'ladi va bu qayta chizish chaqirmaydi. U ekranda ko'rinmaydigan, lekin chizishlar orasida eslab qolinishi kerak bo'lgan narsalar uchun: taymer id'si, AbortController, bosishlar soni, oldingi qiymat. Oddiy let har chizishda yo'qoladi, modul o'zgaruvchisi esa hamma nusxalar uchun bitta. State esa har o'zgarishda qayta chizadi. Qoida: ref.current ni chizish paytida o'qimang va yozmang — faqat hodisa ishlovchisi va effektlarda.

Bu darsda

  • Chizishlar orasida qiymat saqlashning to'rt usulini (let, modul o'zgaruvchisi, state, ref) taqqoslay olasiz.
  • useRef bilan taymer id'sini saqlab, uni to'g'ri to'xtatasiz.
  • Ref o'zgarganda nega ekran yangilanmasligini tushuntira olasiz.
  • "Chizish paytida ref'ga tegmaslik" qoidasini va ESLint react-hooks/refs xabarini bilasiz.
  • vazifalar dagi modul o'zgaruvchilarini (elonTaymeri, sinxron xo'jaligi) ref'larga ko'chirasiz.

Oldin bilishingiz kerak: useEffect III: cleanup, State I: useState, Pure komponentlar va render qoidasi, Taymerlar.

1. Nega bu kerak?

useEffect III darsida taymer effekt ichida yashardi: id effektning o'zgaruvchisida, cleanup uni ko'radi. Lekin taymerni foydalanuvchi boshqarsa-chi? Jasur aka choyxona uchun tugma so'radi: "«Eslatma» bosilsa, 3 daqiqadan keyin «Choy tayyor!» chiqsin. Mehmon fikridan qaytsa — «Bekor qilish»."

Taymer ishlovchida boshlanadi (setTimeout), boshqa ishlovchida to'xtatiladi (clearTimeout(id)). Demak id ikki ishlovchi orasida, ya'ni ikki xil chizish orasida eslab qolinishi kerak. Qayerga saqlaymiz?

vazifalar da bu savol allaqachon turibdi. E'lon matni 3 soniyadan keyin yo'qoladi (elonTaymeri) va u hozircha modul o'zgaruvchisi. Kanondagi izoh buni ochiq aytadi: "Hozircha modul o'zgaruvchisi (TEXNIK-QARZ 29) — 17/#34 da useRef". Server sinxronining "pulti" (sinxronPulti) va tugamagan yozuvlar soni (kutilmoqda) ham shunday. Bugun bu qarzni to'laymiz.

Hayotiy o'xshatish: oshpazning cho'ntak daftarchasi. Unga "3-stol choyi — 14:05 da" deb yozib qo'yadi. Daftarcha zalda hech kimga ko'rinmaydi va unga yozish menyu taxtasini o'zgartirmaydi. Lekin oshpaz uni smena bo'yi o'zida olib yuradi. Menyu taxtasi — state (o'zgarsa, hamma ko'radi). Cho'ntak daftarcha — ref.

2. To'rt joy — qaysi biri to'g'ri

2.1 let — har chizishda yo'qoladi

Birinchi urinish — komponent ichidagi oddiy o'zgaruvchi:

jsx
import { useState } from "react";

function App() {
  const [waiting, setWaiting] = useState(false);
  let timerId; // ❌ har chizishda yangidan undefined
  function remind() {
    timerId = setTimeout(() => console.log("Choy tayyor!"), 300);
    setWaiting(true);
  }
  function cancel() {
    clearTimeout(timerId);
    setWaiting(false);
    console.log("bekor qilindi, timerId =", timerId);
  }
  return (
    <div>
      <p>{waiting ? "Kutilmoqda…" : "Eslatma yo'q"}</p>
      <button onClick={remind}>Eslatma</button>{" "}
      <button onClick={cancel}>Bekor qilish</button>
    </div>
  );
}

Konsolda:

text
bekor qilindi, timerId = undefined
Choy tayyor!

"Bekor qilindi" deyildi, lekin choy baribir "tayyor". Sabab: setWaiting(true) qayta chizishga olib keldi. Komponent funksiyasi boshidan chaqirildi va let timerId yana undefined bo'ldi. cancel — yangi chizishning funksiyasi va u yangi, bo'sh timerId ni ko'radi. Taymer id'si birinchi chizish bilan birga yo'qoldi.

Qadam Chizish timerId Nima bo'ldi
«Eslatma» 1-chizishning remind 12 taymer boshlandi, setWaiting(true)
qayta chizish 2-chizish undefined yangi let
«Bekor qilish» 2-chizishning cancel undefined clearTimeout(undefined) — hech narsa
300 ms — — "Choy tayyor!"

(12 — namuna: brauzer taymerga istalgan raqam beradi.)

2.2 Modul o'zgaruvchisi — hamma uchun bitta

Ikkinchi urinish — let timerId ni komponentdan tashqariga chiqarish. Endi u yo'qolmaydi. Lekin choyxonada ikki stol bor va har stolda o'z Reminder i:

jsx
import { useState } from "react";

let timerId; // ❌ modul darajasida — ikkala Reminder uchun BITTA

function Reminder({ table }) {
  const [waiting, setWaiting] = useState(false);
  function remind() {
    const done = () => console.log(`Choy tayyor! (${table})`);
    timerId = setTimeout(done, 300);
    setWaiting(true);
  }
  function cancel() {
    clearTimeout(timerId);
    setWaiting(false);
  }
  return (
    <p>
      {table}: {waiting ? "kutilmoqda" : "—"}{" "}
      <button onClick={remind}>Eslatma {table}</button>{" "}
      <button onClick={cancel}>Bekor {table}</button>
    </p>
  );
}

function App() {
  return (
    <div>
      <Reminder table="A" />
      <Reminder table="B" />
    </div>
  );
}

Konsolda:

text
Choy tayyor! (A)

A stoli bekor qildi — lekin A'ning choyi "tayyor" bo'ldi, B'niki esa jim yo'qoldi. B'ning remind i umumiy timerId ni o'z taymeri bilan almashtirgan edi. «Bekor A» esa B'ning taymerini to'xtatdi. Modul o'zgaruvchisi komponentning har nusxasiga emas, butun faylga tegishli. vazifalar dagi elonTaymeri ning muammosi ham shu: hozir App bitta, lekin testlarda (bir faylda bir necha render(<App />)) yoki kelajakda ikkita App bo'lsa, ular bitta taymerni bo'lishadi.

2.3 State — ishlaydi, lekin ortiqcha

Uchinchi urinish — const [timerId, setTimerId] = useState(null). Ishlaydi: state chizishlar orasida saqlanadi va har nusxaning o'z state'i bor. Lekin setTimerId(id) qayta chizish chaqiradi — taymer id'si ekranda ko'rinmasa ham. Har eslatmada bitta keraksiz chizish. Bundan tashqari, state "snapshot" (State II): setTimerId dan keyin o'sha ishlovchida timerId hali eski qiymat.

2.4 Ref — aynan kerakli

To'rtinchi joy — useRef:

jsx
import { useRef, useState } from "react";

function App() {
  const [waiting, setWaiting] = useState(false);
  const timerRef = useRef(null); // { current: null } — doim O'SHA
  function remind() {
    const done = () => console.log("Choy tayyor!");
    timerRef.current = setTimeout(done, 300);
    setWaiting(true);
  }
  function cancel() {
    clearTimeout(timerRef.current);
    setWaiting(false);
    console.log("bekor qilindi");
  }
  return (
    <div>
      <p>{waiting ? "Kutilmoqda…" : "Eslatma yo'q"}</p>
      <button onClick={remind}>Eslatma</button>{" "}
      <button onClick={cancel}>Bekor qilish</button>
    </div>
  );
}

Konsolda:

text
bekor qilindi

Ekranda:

text
Eslatma yo'q
Eslatma Bekor qilish

"Choy tayyor!" yo'q — taymer haqiqatan to'xtadi. Qayta chizishdan keyin ham timerRef — o'sha obyekt va uning current ida birinchi chizishda yozilgan id turibdi.

Yig'ma jadval:

Joy Chizishlar orasida saqlanadimi Har nusxaga alohidami O'zgarsa qayta chizadimi
let (komponent ichida) —
modul o'zgaruvchisi
useState
useRef

Ref — yagona qator, uchala ustunda ham kerakli javob bor.

Qaror savollar bilan:

flowchart TD
  Q1{"O'zgarsa, ekran yangilanishi kerakmi?"}
  Q1 -- "ha" --> S["useState"]
  Q1 -- "yo'q" --> Q2{"Chizishlar orasida eslab qolish kerakmi?"}
  Q2 -- "yo'q" --> L["oddiy const / let"]
  Q2 -- "ha" --> Q3{"Har nusxaga alohidami?"}
  Q3 -- "ha" --> R["useRef"]
  Q3 -- "yo'q, butun ilovaga bitta" --> M["modul darajasi"]

Diagrammani yuqoridan o'qing: birinchi savol — eng muhimi. Ekranga ta'sir qiladigan narsa doim state. Qolganlari uchun ikki savol — va ko'pincha javob ref.

3. useRef qanday ishlaydi

3.1 Quti

Ref (reference — havola) — React komponentga beradigan oddiy obyekt: { current: boshlang'ich }. useRef(0) birinchi chizishda { current: 0 } ni yaratadi va keyingi har chizishda aynan o'sha obyektni qaytaradi. Uning current xususiyatini siz istagancha o'qiysiz va yozasiz — React bunga aralashmaydi va kuzatmaydi ham.

React ichida useRef ni useState orqali yozsa bo'lardi:

jsx
// Tushunish uchun model (React aslida shunga yaqin):
function useRef(initial) {
  const [ref] = useState({ current: initial }); // set… chaqirilmaydi
  return ref;
}

State ichidagi obyekt — har chizishda o'sha. Biz uning ichini o'zgartiramiz, lekin setRef ni chaqirmaymiz — shuning uchun React qayta chizmaydi. Obyekt va massivlarni immutable yangilash darsida "state obyektini joyida o'zgartirmang, React sezmaydi" degan edik. Ref aynan shu xususiyatni foydali tomonga buradi: sezmasligi kerak bo'lgan narsa uchun.

3.2 Ref o'zgaradi — ekran yo'q

jsx
import { useRef } from "react";

function App() {
  const clicks = useRef(0);
  console.log("chizildi");
  function add() {
    clicks.current++;
    console.log("ref:", clicks.current);
  }
  return (
    <div>
      <button onClick={add}>+1</button>{" "}
      <button onClick={() => console.log("jami:", clicks.current)}>
        Ko'rsat
      </button>
    </div>
  );
}

Konsolda:

text
chizildi
ref: 1
ref: 2
jami: 2

"chizildi" — faqat bir marta, ochilishda. Ikki bosish clicks.current ni 2 ga yetkazdi, lekin komponent qayta chizilmadi. Agar clicks.current ni JSX'da ko'rsatganimizda, ekranda abadiy 0 turardi. Shuning uchun ref — ekranda ko'rinmaydigan narsa uchun.

Tekshirib ko'ring: useRef qaytargan obyektni effekt massiviga yozish kerakmi? timerRef.current ni-chi?

Javob

timerRef obyektini — yo'q: u barqaror, har chizishda o'sha (useEffect II darsidagi "barqaror qiymatlar"). ESLint ham so'ramaydi. timerRef.current ni esa massivga yozish ma'nosiz: React massivni chizishdan keyin solishtiradi, current esa chizishdan tashqarida o'zgaradi va qayta chizish chaqirmaydi. Effekt uning o'zgarishini "ko'rmaydi".

4. Qoida: chizish paytida ref'ga tegmang

4.1 Nega

Ref'ni hodisa ishlovchisida va effektda o'qing va yozing. Chizish paytida (komponent tanasida, JSX'da) — yo'q. Sabab — Pure komponentlar qoidasi: komponent toza funksiya, bir xil kirish — bir xil natija. ref.current esa React bilmaydigan, istalgan payt o'zgaradigan qiymat. Uni JSX'da ishlatsangiz, ekran unga ergashmaydi (yuqoridagi 0). Chizish paytida yozsangiz — StrictMode ikki marta chaqiradi va qiymat ikki baravar o'sadi.

ESLint (plagin 7.1.1, React Compiler qoidasi) buni ushlaydi. Faylda chizishlarni ref bilan sanashga urinish (qisqartirildi):

jsx
export function Counter() {
  const clicks = useRef(0);
  const [, setN] = useState(0);
  clicks.current = clicks.current + 1;
  return <p onClick={() => setN(1)}>Chizildi: {clicks.current}</p>;
}
text
  6:3   error  Error: Cannot access refs during render

React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).

> 6 |   clicks.current = clicks.current + 1;
    |   ^^^^^^^^^^^^^^ Cannot update ref during render

"Chizish paytida ref'larga murojaat qilib bo'lmaydi. Ref'lar — chizish uchun kerak bo'lmagan qiymatlar. Ularga faqat chizishdan tashqarida — hodisa ishlovchilari yoki effektlarda — murojaat qiling. Chizish paytida current ni o'qish komponentning kutilgandek yangilanmasligiga olib keladi." Shu faylda yana ikki xato bor: 6-qatordagi o'qish va 7-qatordagi JSX'dagi clicks.current. Qoida nomi — react-hooks/refs.

4.2 Yagona istisno: lazy init (dangasa boshlash)

Bitta holatda chizish paytida ref'ga yozish mumkin: bir marta, bo'sh bo'lsa, qimmat obyektni yaratish:

jsx
const playerRef = useRef(null);
if (playerRef.current === null) {
  playerRef.current = new AudioPlayer(); // faqat birinchi chizishda
}

useRef(new AudioPlayer()) deb yozsangiz, new AudioPlayer() har chizishda chaqiriladi (natijasi faqat birinchi marta olinadi). Bu shart — natija har doim bir xil, demak toza. React hujjati bu naqshni ruxsat etadi.

5. Oldingi qiymat

5.1 Effektda solishtirish

"Mehmon kategoriyani qaysidan qaysiga almashtirdi?" — analytics uchun. Oldingi qiymat ekranga chiqmaydi, faqat effektda kerak — ref uchun ideal:

jsx
import { useEffect, useRef, useState } from "react";

function App() {
  const [category, setCategory] = useState("taom");
  const prevRef = useRef(category);
  useEffect(() => {
    if (prevRef.current !== category) {
      console.log(`o'tish: ${prevRef.current} → ${category}`);
    }
    prevRef.current = category; // keyingi safar uchun
  }, [category]);
  return (
    <div>
      <p>Bo'lim: {category}</p>
      <button onClick={() => setCategory("ichimlik")}>
        Ichimlik
      </button>{" "}
      <button onClick={() => setCategory("shirinlik")}>
        Shirinlik
      </button>
    </div>
  );
}

Konsolda:

text
o'tish: taom → ichimlik
o'tish: ichimlik → shirinlik

Ref effekt ichida o'qiladi va yoziladi — chizishdan tashqarida. Ochilishda hech narsa chiqmadi: prevRef.current va category bir xil edi.

5.2 Ekranda kerak bo'lsa — state

Oldingi qiymat ekranda kerak bo'lsa ("narx 30 000 dan 35 000 ga oshdi ↑"), ref yaramaydi — chizish paytida o'qish taqiqlangan. U holda oldingi qiymat state'da saqlanadi. U chizish paytida, shart bilan yangilanadi — React hujjatidagi "oldingi chizishdagi ma'lumotni saqlash" naqshi:

jsx
import { useState } from "react";

function Price({ price }) {
  const [prev, setPrev] = useState(price);
  const [trend, setTrend] = useState("");
  if (price !== prev) {
    setPrev(price); // chizish paytida, faqat price o'zgarganda
    setTrend(price > prev ? "↑" : "↓");
  }
  return <p>Osh: {price} so'm {trend}</p>;
}

function App() {
  const [price, setPrice] = useState(30000);
  return (
    <div>
      <Price price={price} />
      <button onClick={() => setPrice(35000)}>Narxni oshir</button>
    </div>
  );
}

Ekranda:

text
Osh: 35000 so'm ↑
Narxni oshir

Bu naqsh kam uchraydi va o'qish biroz qiyin. Avval o'ylang: balki oldingi qiymatni ota'da, hodisa paytida hisoblash mumkindir? Lekin ref'ni JSX'da o'qishdan ko'ra u to'g'ri.

6. TypeScript'da ref

useRef ning turi boshlang'ich qiymatdan olinadi. Taymer id'si uchun turni aniq yozish kerak — setTimeout brauzerda son, Node'da obyekt qaytaradi. ReturnType<typeof setTimeout> ikkalasiga ham to'g'ri (Utility types):

tsx
import { useRef, useState } from "react";

export default function App() {
  const [notice, setNotice] = useState("");
  const timer = useRef<ReturnType<typeof setTimeout>>(undefined);
  function announce(text: string) {
    clearTimeout(timer.current); // undefined bo'lsa ham xavfsiz
    setNotice(text);
    timer.current = setTimeout(() => setNotice(""), 3000);
  }
  return (
    <button onClick={() => announce("Saqlandi")}>
      Saqlash {notice}
    </button>
  );
}

Ekranda:

text
Saqlash

useRef<T>(undefined) — React 19 turlarida boshlang'ich qiymat majburiy. 18-versiya turlarida useRef<T>() bo'sh qavslar bilan ham yozilardi — eski kodda uchraydi. Hozir u tip xatosi:

tsx
import { useRef } from "react";

export function App() {
  const timer = useRef<number>(); // ❌ argumentsiz
  return <p>{typeof timer}</p>;
}

Xato:

text
TS2554: Expected 1 arguments, but got 0.

"1 ta argument kutilgan edi, 0 ta berildi." Tuzatish — useRef<number>(undefined) yoki, DOM ref'larida, useRef<HTMLInputElement>(null) (useRef II). clearTimeout(undefined) — xato emas, shunchaki hech narsa qilmaydi. Bu aynan vazifalar dagi elonQil ning shakli.

7. Vazifalar qadami: modul o'zgaruvchilari ref'larga

7.1 Reja

Yangi branch: refactor/reflar. Ikki dars (shu va useRef II) — bitta commit. Bugun — "xo'jalik" ref'lari: e'lon taymeri va server sinxronining pulti. Keyingi darsda — DOM ref'lari (fokus, dialog) va commit.

7.2 E'lon taymeri

diff
-// Taymer id — ekranga chiqmaydi, render'ga ta'sir qilmaydi: state emas.
-// Hozircha modul o'zgaruvchisi (TEXNIK-QARZ 29) — 17/#34 da useRef
-let elonTaymeri: ReturnType<typeof setTimeout> | undefined;
+  // --- Ref'lar (17/#33–#34): o'zgarsa qayta chizilmaydigan xotira ---
+  // Taymer id — ekranga chiqmaydi (#28 da modul o'zgaruvchisi edi)
+  const elonTaymeri = useRef<ReturnType<typeof setTimeout>>(undefined);
@@
   function elonQil(matn: string) {
-    clearTimeout(elonTaymeri);
+    clearTimeout(elonTaymeri.current);
     setElon(matn);
     if (matn !== "") {
-      elonTaymeri = setTimeout(() => setElon(""), ELON_MUDDATI);
+      elonTaymeri.current = setTimeout(() => setElon(""), ELON_MUDDATI);
     }
   }

Birinchi uch qator modul darajasidan o'chdi, useRef esa App ichida, boshqa hooklar bilan birga paydo bo'ldi. elonQil — hodisa ishlovchilaridan chaqiriladi, demak ref chizishdan tashqarida o'qiladi va yoziladi.

7.3 Server sinxronining xo'jaligi

server-sinxron.ts da ikki modul o'zgaruvchisi bor edi: yo'ldagi so'rovning AbortController i va tugamagan yozuvlar soni. Ular bitta turga yig'ildi va App ning ref'iga ko'chdi (kanon):

ts
/** Render'ga ta'sir qilmaydigan xotira — useRef ichida */
export type SinxronXojaligi = {
  pult: AbortController | null; // yo'ldagi solishtirish so'rovi
  kutilmoqda: number; // tugamagan yozuvlar
};
tsx
  const xojalik = useRef<SinxronXojaligi>({
    pult: null,
    kutilmoqda: 0,
  });

Modul funksiyalari ref'ni argument sifatida oladi (RefObject<SinxronXojaligi>), o'zgaruvchiga to'g'ridan-to'g'ri murojaat qilmaydi:

diff
-export function sinxronniTox(): void {
-  sinxronPulti?.abort();
+export function sinxronniTox(
+  xojalik: RefObject<SinxronXojaligi>,
+): void {
+  xojalik.current.pult?.abort();
 }

Effekt cleanup'i ham o'zgardi: return sinxronniTox → return () => sinxronniTox(xojalik). Fayl boshidagi izoh nega shunday qilinganini aytadi (kanon): "App'ning useRef'i (17/#33; #28 da modul o'zgaruvchisi edi — ikkita App bo'lsa, bitta hisoblagichni bo'lishardi)". Bu «Modul o'zgaruvchisi — hamma uchun bitta» bo'limidagi A va B stollari muammosining vazifalar dagi shakli.

xojalik ning ichida ikki maydon bor — ikki alohida ref emas. Ular birga o'zgaradi va birga uzatiladi. Ref — istalgan qiymat uchun quti, jumladan obyekt uchun ham.

Diqqat: Bu qadam o'zi alohida commit emas. Tekshiruvlar va commit — useRef II darsi oxirida.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
taymer to'xtamaydi, timerId = undefined id let da, qayta chizishda yo'qoldi useRef
bir komponent boshqasining taymerini to'xtatdi modul o'zgaruvchisi useRef (har nusxaga alohida)
ref o'zgardi, ekran eski ref qayta chizish chaqirmaydi ekranda kerak — state
Cannot access refs during render JSX yoki komponent tanasida ref.current ishlovchi yoki effektga ko'chiring
StrictMode'da hisoblagich ikki baravar chizish paytida ref'ga yozilgan effekt yoki ishlovchida yozing
Expected 1 arguments, but got 0 (TS) React 19 turlarida useRef<T>() useRef<T>(undefined) yoki null

9. Mashqlar

Mashqlar kurs/mashqlar/17/33-useref-xotira/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): state yoki ref?

Har qiymat uchun tanlang: state yoki ref.

  • Savatdagi taomlar soni (ekranda ko'rinadi):
  • setInterval qaytargan id:
  • Yo'ldagi so'rovning AbortController i:
  • Qidiruv maydonidagi matn (controlled input):
Yechim

Ekranda ko'rinadigan va o'zgarganda ekran yangilanishi kerak bo'lgan narsa — state (taomlar soni, maydon matni). Ekranga chiqmaydigan "xo'jalik" ma'lumoti — ref: taymer id'si, AbortController. Savol: "bu o'zgarsa, ekran o'zgarishi kerakmi?" Ha — state, yo'q — ref.

2-mashq (o'rta): bosishlar statistikasi

Jasur aka bilmoqchi: mehmon «Osh +» ni necha marta bosdi? Bu son ekranda ko'rinmasligi kerak — faqat «Hisobot» bosilganda konsolga chiqsin. Sardor uni state'da saqladi va har bosishda keraksiz chizish bo'lyapti (konsoldagi "chizildi" qatorlari). Ref bilan qayta yozing: "chizildi" faqat bir marta chiqsin. Ishora: «Ref o'zgaradi — ekran yo'q» bo'limi.

jsx
import { useState } from "react";

function App() {
  const [clicks, setClicks] = useState(0);
  console.log("chizildi");
  const add = () => setClicks(clicks + 1);
  return (
    <div>
      <button onClick={add}>Osh +</button>{" "}
      <button onClick={() => console.log("bosishlar:", clicks)}>
        Hisobot
      </button>
    </div>
  );
}

Konsolda:

text
chizildi
chizildi
chizildi
chizildi
bosishlar: 3
Yechim
jsx
import { useRef } from "react";

function App() {
  const clicks = useRef(0);
  console.log("chizildi");
  const report = () => console.log("bosishlar:", clicks.current);
  return (
    <div>
      <button onClick={() => clicks.current++}>Osh +</button>{" "}
      <button onClick={report}>Hisobot</button>
    </div>
  );
}

Konsolda:

text
chizildi
bosishlar: 3

To'rt chizish o'rniga bitta. clicks.current++ — ishlovchida, demak qoida buzilmadi. Agar keyinroq Jasur aka sonni ekranda ko'rmoqchi bo'lsa — state'ga qaytish kerak: ref ekranni yangilamaydi.

3-mashq (qiyin): ikki stolning eslatmasi

«Modul o'zgaruvchisi — hamma uchun bitta» bo'limidagi Reminder ni tuzating: har stolning o'z taymeri bo'lsin. Blok: A va B eslatmani yoqadi, A bekor qiladi. Natijada faqat Choy tayyor! (B) chiqishi kerak. Qo'shimcha talab: eslatma yoqilganda eski taymer bo'lsa — avval uni to'xtating (ikki marta bosilsa, ikki taymer bo'lmasin). Ishora: clearTimeout(undefined) yoki clearTimeout(null) xavfsiz.

Yechim
jsx
import { useRef, useState } from "react";

function Reminder({ table }) {
  const [waiting, setWaiting] = useState(false);
  const timerRef = useRef(null); // har Reminder'ning o'z qutisi
  function remind() {
    clearTimeout(timerRef.current); // eski taymer bo'lsa — to'xtat
    const done = () => {
      console.log(`Choy tayyor! (${table})`);
      setWaiting(false);
    };
    timerRef.current = setTimeout(done, 300);
    setWaiting(true);
  }
  function cancel() {
    clearTimeout(timerRef.current);
    setWaiting(false);
  }
  return (
    <p>
      {table}: {waiting ? "kutilmoqda" : "—"}{" "}
      <button onClick={remind}>Eslatma {table}</button>{" "}
      <button onClick={cancel}>Bekor {table}</button>
    </p>
  );
}

function App() {
  return (
    <div>
      <Reminder table="A" />
      <Reminder table="B" />
    </div>
  );
}

Konsolda:

text
Choy tayyor! (B)

Ekranda:

text
A: — Eslatma A Bekor A
B: — Eslatma B Bekor B

Har Reminder o'z useRef qutisini oldi — A va B bir-birining taymeriga tegmaydi. remind boshidagi clearTimeout "ikki marta bosish" holatini yopadi. Taymer tugaganda setWaiting(false) — B qatori ham "—" ga qaytdi. Bitta kamchilik qoldi: komponent ekrandan olib tashlansa, yo'ldagi taymer ishlayveradi. Buni effekt cleanup'i bilan yopish — 4-mashqdan keyingi o'zingizning qadamingiz bo'lsin (useEffect III).

4-mashq: Vazifalar qadami

vazifalar da refactor/reflar branch'ini oching. elonTaymeri va server-sinxron.ts dagi ikki modul o'zgaruvchisini «Vazifalar qadami: modul o'zgaruvchilari ref'larga» bo'limidagidek App ning ref'lariga ko'chiring.

Yechim

Tekshiruv ro'yxati:

  • grep -n "^let " src/App.tsx src/server-sinxron.ts — bo'sh (modul darajasida let qolmadi).
  • npm run tip — sinxronniTox, sinxronla, yozuvniYubor chaqiruvlarida xojalik uzatilgan (Sozlagichlar turida endi xojalik maydoni bor — unutilsa, tsc aytadi).
  • npm run lint — react-hooks/refs xatosi yo'q: ref'lar faqat ishlovchi va effektlarda.
  • Commit hali yo'q — useRef II darsida.

10. Real ishda

  • Taymerlar, AbortController, WebSocket ulanishi, uchinchi tomon kutubxonasining nusxasi (xarita, pleyer, grafik) — ref'larda saqlanadigan odatiy narsalar.
  • Debounce va throttle (Debounce va throttle) React'da ko'pincha ref ichidagi taymer bilan yoziladi; tayyor hooklar ham shunday (Custom hooklar II).
  • Kod ko'rigida: komponent faylida modul darajasidagi let — deyarli har doim xato belgisi. Savol: "ikkita nusxa bo'lsa nima bo'ladi?"
  • Intervyu savollari: "useRef va useState farqi?", "nega ref o'zgarganda komponent qayta chizilmaydi?", "ref'ni render paytida o'qish nega yomon?".

Xulosa

  • useRef(boshlang'ich) — { current } qutisi: har chizishda o'sha obyekt, o'zgarsa qayta chizish yo'q.
  • let har chizishda yo'qoladi, modul o'zgaruvchisi hamma nusxaga bitta, state ortiqcha chizadi — ref ekranga chiqmaydigan xotira uchun.
  • ref.current — faqat ishlovchi va effektlarda; chizish paytida emas (ESLint react-hooks/refs). Istisno — bir martalik lazy init (dangasa boshlash).
  • Oldingi qiymat: effektda kerak — ref; ekranda kerak — state.
  • TypeScript: useRef<ReturnType<typeof setTimeout>>(undefined) — React 19 da boshlang'ich qiymat majburiy.
  • vazifalar: elonTaymeri va sinxron xo'jaligi (pult, kutilmoqda) modul o'zgaruvchisidan App ning ref'lariga ko'chdi.

Keyingi dars: useRef II: DOM elementlari va ref prop (React 19) — ref'ning ikkinchi vazifasi: DOM elementiga havola, fokus, ref callback va React 19 dagi ref prop.

Manbalar

  • React: "Referencing Values with Refs" — react.dev/learn/referencing-values-with-refs
  • React: useRef — "Do not write or read ref.current during rendering", "Avoiding recreating the ref contents" — react.dev/reference/react/useRef
  • React: useState — "Storing information from previous renders" — react.dev/reference/react/useState
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
useRef I: qayta chizish chaqirmaydigan xotira — taymer id, hisoblagich, oldingi qiymat — IlmHamroh