IlmHamroh
JavaScript Full-stack/17-qism. React asoslari34/45-dars20 daqiqa
Mundarija (31)

useRef II: DOM elementiga ref, fokus va React 19 dagi ref prop

Qisqacha: <input ref={inputRef} /> yozsangiz, React commit paytida haqiqiy DOM elementini inputRef.current ga qo'yadi va element olib tashlanganda null qiladi. Shunda React bajarmaydigan DOM ishlarini qilasiz: focus(), select(), scrollIntoView(), showModal(), o'lcham olish. Ref chizish paytida hali null — undan ishlovchi va effektlarda foydalaniladi. ref ga funksiya ham berish mumkin (ref callback). React 19 da u cleanup qaytara oladi — ro'yxat uchun ref Map shu bilan toza yoziladi. React 19 da ref — funksiya komponent uchun oddiy prop: function Input({ ref }). Eski forwardRef endi shart emas.

Bu darsda

  • DOM elementiga ref ulab, fokus va matn belgilashni boshqarasiz.
  • Ref qachon null va qachon element ekanini Render → Commit → Effekt tartibida ko'rsatasiz.
  • Ref callback yozasiz va React 19 dagi ref cleanup'ni ishlatasiz.
  • O'z komponentingizdan ref prop orqali ichki elementni ochib berasiz (forwardRef siz).
  • Ro'yxatdagi ko'p elementlar uchun ref Map tuzasiz va vazifalar dagi getElementById lardan qutulasiz.

Oldin bilishingiz kerak: useRef I, useEffect I, Fokus va tahrir atributlari, <dialog> va Popover API JS'dan.

1. Nega bu kerak?

React nima darsida qoida qo'ygan edik: React boshqaradigan joyda DOM'ni qo'lda o'zgartirmang. Lekin ba'zi ishlarni React umuman qilmaydi — ular holat emas, harakat. "Fokusni shu maydonga qo'y", "shu qatorga aylantir", "dialogni ochiq holda ko'rsat", "maydon kengligini o'lcha". JSX'da focused={true} degan atribut yo'q.

Bobur «Bahor» sahifasini faqat klaviatura bilan ishlatadi. U "Tahrirlash" ni bosganda fokus tahrir maydoniga o'tishi kerak — aks holda u Tab'ni o'n marta bosib, maydonni qidiradi. Vazifa o'chirilganda fokus qo'shni qatorga o'tishi kerak — aks holda u sahifa boshiga "uloqtiriladi". Malika (ekran o'quvchi) uchun ham shunday.

vazifalar da bu ishlar hozir fokus.ts faylida: document.getElementById(...) va closest("li") bilan. Kanondagi izohlar o'zi aytadi: "Ref'lar 17/#34 da — hozircha id bilan". Bugun — o'sha #34.

Hayotiy o'xshatish: mehmonxonada mehmonga xona kaliti beriladi. Kalit — xonaning o'zi emas, lekin u bilan xonaga kirib, chiroqni yoqish mumkin. Ref — DOM elementining kaliti: React elementni quradi va kalitni sizning qutingizga (ref.current) qo'yadi.

2. DOM elementiga ref

2.1 Fokus tugmasi

useRef I darsidagi qutini endi ref atributiga beramiz:

jsx
import { useRef } from "react";

function App() {
  const inputRef = useRef(null);
  function startNote() {
    inputRef.current.focus(); // DOM metodi
    console.log("fokusda:", document.activeElement.id);
  }
  return (
    <div>
      <input id="izoh" ref={inputRef} placeholder="Izoh" />
      <button onClick={startNote}>Izoh yozish</button>
    </div>
  );
}

Konsolda:

text
fokusda: izoh

Uch qadam:

  1. useRef(null) — bo'sh quti. DOM ref'i uchun boshlang'ich qiymat doim null.
  2. ref={inputRef} — React'ga: "shu <input> ning DOM elementini qutiga qo'y".
  3. Ishlovchida inputRef.current — haqiqiy HTMLInputElement, unda DOM'dan tanish hamma metod bor: focus(), select(), value, getBoundingClientRect().

ref — maxsus atribut: u DOM'ga yozilmaydi va props sifatida emas, React'ning o'zi tomonidan ishlatiladi (xuddi key kabi).

2.2 Ref qachon to'ladi

Ref chizish paytida emas, commit paytida to'ladi — React elementni DOM'ga qo'ygan zahoti:

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

function App() {
  const inputRef = useRef(null);
  console.log("render:", inputRef.current);
  useEffect(() => {
    console.log("effekt:", inputRef.current.tagName);
  }, []);
  return <input ref={inputRef} />;
}

Konsolda:

text
render: null
effekt: INPUT
Bosqich inputRef.current Ishlatsa bo'ladimi
Render (birinchi) null element hali yo'q
Commit React elementni qo'yadi —
Effekt <input>
Hodisa ishlovchisi <input>
Cleanup (olib tashlanganda) React null qiladi —
sequenceDiagram
  participant K as Komponent
  participant R as React DOM
  participant Q as inputRef.current
  K->>R: Render: JSX qaytadi (Q = null)
  R->>R: Commit: input DOM'ga qo'yildi
  R->>Q: Q = input elementi
  R->>K: Effekt: Q.focus() mumkin
  Note over R: element olib tashlandi
  R->>Q: Q = null

Diagrammada ref'ni React'ning o'zi to'ldiradi va bo'shatadi — siz faqat o'qiysiz. Shu sababli DOM ref'i bilan ishlash joyi — effekt va ishlovchilar. Chizish paytida ref'ni o'qimaslik qoidasi (useRef I) bu yerda ikki karra to'g'ri: birinchi chizishda u hatto null.

2.3 Ochilganda fokus — ref + effekt

"Tahrir qatori paydo bo'lganda — fokus maydonda, matn belgilangan" — bu chizish natijasi. Demak effekt (useEffect I). Effekt DOM elementi bilan sinxronlaydi — tashqi tizim:

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

function EditRow({ text }) {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
    inputRef.current.select(); // yozish eski matnni almashtiradi
  }, []);
  return (
    <input ref={inputRef} defaultValue={text} aria-label="Matn" />
  );
}

function App() {
  const [editing, setEditing] = useState(false);
  const where = () => console.log(document.activeElement.tagName);
  return (
    <div>
      {editing ? (
        <EditRow text="Non olish" />
      ) : (
        <button onClick={() => setEditing(true)}>Tahrirlash</button>
      )}
      <button onClick={where}>Fokus qayerda?</button>
    </div>
  );
}

Blokni ishga tushiring: «Tahrirlash» dan keyin kursor maydonda, "Non olish" belgilangan. Bobur darhol yangi matn yozishi mumkin. Massiv [] — faqat qator paydo bo'lganda. Agar qator qayta chizilsa (masalan, ota yangilansa), belgilash buzilmaydi.

Maslahat: HTML'da autoFocus atributi ham bor (<input autoFocus />) va React uni qo'llab-quvvatlaydi. U sahifa ochilganda ishlaydi va matnni belgilamaydi. Shartli paydo bo'ladigan maydon va select() uchun ref + effekt aniqroq.

2.4 Boshqa DOM ishlari

Fokusdan tashqari, ref bilan qilinadigan odatiy ishlar:

Vazifa DOM metodi Qayerda
matnni belgilash select() ishlovchi / effekt
elementga aylantirish scrollIntoView({ block: "nearest" }) ishlovchi
modal dialog showModal(), close() ishlovchi
o'lcham getBoundingClientRect() effekt (useLayoutEffect)
video play(), pause() ishlovchi

Aylantirish va dialog sayt oynasidagi haqiqiy brauzerda ishlaydi:

jsx
import { useRef } from "react";

const MENU = ["Osh", "Lag'mon", "Manti", "Somsa", "Chuchvara",
  "Norin", "Shashlik", "Ko'k choy"];

function App() {
  const lastRef = useRef(null);
  const dialogRef = useRef(null);
  return (
    <div>
      <button onClick={() => lastRef.current.scrollIntoView()}>
        Oxirgi taomga
      </button>{" "}
      <button onClick={() => dialogRef.current.showModal()}>
        Bron oynasi
      </button>
      <ul style={{ height: 80, overflow: "auto" }}>
        {MENU.map((d, i) => (
          <li key={d} ref={i === MENU.length - 1 ? lastRef : null}>
            {d}
          </li>
        ))}
      </ul>
      <dialog ref={dialogRef}>
        <p>Stol bron qilindi.</p>
        <button onClick={() => dialogRef.current.close()}>
          Yopish
        </button>
      </dialog>
    </div>
  );
}

«Ishga tushir» dan keyin «Oxirgi taomga» ni bosing — ro'yxat "Ko'k choy" gacha aylanadi. «Bron oynasi» — <dialog> modal ochiladi, fon bosilmaydi (dialog va Popover API). E'tibor bering: dialogning ochiq yoki yopiqligini React emas, brauzer boshqaradi — biz faqat buyruq beramiz.

Diqqat: Ref orqali o'qing va buyruq bering, lekin React chizgan DOM'ni o'zgartirmang: ref.current.remove(), appendChild, textContent = … — React keyingi chizishda o'z daraxtiga ishonadi va xato berishi yoki o'zgarishingizni o'chirib yuborishi mumkin. Matn va tuzilma — state orqali.

Tekshirib ko'ring: Nega <input ref={inputRef} /> dan keyin darhol, komponent tanasida inputRef.current.focus() yozib bo'lmaydi?

Javob

Komponent tanasi — chizish. Birinchi chizishda <input> hali DOM'da yo'q, inputRef.current — null: TypeError: Cannot read properties of null (reading 'focus'). Bundan tashqari, focus() — yon ta'sir, chizish esa toza bo'lishi kerak. To'g'ri joy — effekt (paydo bo'lganda) yoki ishlovchi (bosilganda).

3. Ref callback

3.1 Funksiya ham ref

ref ga obyekt o'rniga funksiya berish mumkin. React uni element DOM'ga qo'yilganda element bilan chaqiradi. Ref callback (ref callback) — ref={(el) => …} shaklidagi funksiya: element ulanganda chaqiriladi.

React 19 da ref callback cleanup funksiyasi qaytara oladi — element olib tashlanganda React aynan uni chaqiradi (effekt cleanup'i kabi). StrictMode bilan:

jsx
import { StrictMode, useState } from "react";
import { createRoot } from "react-dom/client";

function App() {
  const [show, setShow] = useState(true);
  return (
    <div>
      {show && (
        <input
          ref={(el) => {
            console.log("ulandi:", el.tagName);
            return () => console.log("uzildi");
          }}
        />
      )}
      <button onClick={() => setShow(false)}>Yashir</button>
    </div>
  );
}

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>,
);

Konsolda:

text
ulandi: INPUT
uzildi
ulandi: INPUT
uzildi

Dastlabki uch qator — StrictMode sinovi: effektlar kabi ref callback ham dev'da ulandi → uzildi → ulandi qilinadi (bu React 19 da qo'shildi). To'rtinchisi — «Yashir»: <input> olib tashlandi, cleanup ishladi.

React 19 dan oldin cleanup yo'q edi: element olib tashlanganda React callback'ni null bilan qayta chaqirardi. Eski kodda if (el === null) { … } tekshiruvini ko'rasiz. React 19 cleanup qaytarilmasa, hali ham shunday qiladi — shuning uchun kanondagi tugmaRef boshida null ni tekshiradi.

3.2 TypeScript'dagi tuzoq

Cleanup'ning qo'shilishi bitta qadimgi odatni buzdi. Qisqa arrow funksiya qiymat qaytaradi:

tsx
let current: HTMLInputElement | null = null;

export function App() {
  return <input ref={(el) => (current = el)} />;
}

Xato:

text
TS2322: Type '(el: HTMLInputElement | null) => HTMLInputElement | null' is not assignable to type 'Ref<HTMLInputElement> | undefined'.

"(el) => HTMLInputElement | null turidagi funksiyani Ref<HTMLInputElement> ga berib bo'lmaydi." Qavs ichidagi tenglik qiymat qaytaradi (el). React 19 turlari esa ref callback'dan faqat cleanup funksiyasi yoki hech narsa kutadi. Tuzatish — jingalak qavs: ref={(el) => { current = el; }}.

4. Ro'yxat uchun ref Map

4.1 Muammo

useRef — hook, uni sikl ichida chaqirib bo'lmaydi (Hooklar qoidalari). Ro'yxatdagi har qatorga alohida ref kerak bo'lsa-chi? Masalan, vazifa o'chirilganda fokus qo'shni qatorning tugmasiga o'tishi kerak — qaysi qator, oldindan noma'lum.

Yechim — bitta ref, ichida Map (Map): kalit — id, qiymat — element. Ref callback element ulanganda set, cleanup'da delete qiladi:

jsx
import { useRef } from "react";

const DISHES = [
  { id: 1, name: "Osh" },
  { id: 2, name: "Lag'mon" },
  { id: 3, name: "Manti" },
];

function App() {
  const buttons = useRef(new Map()); // id → <button>
  function focusDish(id) {
    buttons.current.get(id)?.focus();
    console.log("fokusda:", document.activeElement.textContent);
    console.log("Map'da:", buttons.current.size, "ta");
  }
  return (
    <div>
      <ul>
        {DISHES.map((d) => (
          <li key={d.id}>
            <button
              ref={(el) => {
                buttons.current.set(d.id, el);
                return () => buttons.current.delete(d.id);
              }}
            >
              {d.name}
            </button>
          </li>
        ))}
      </ul>
      <button onClick={() => focusDish(2)}>Lag'mon'ga o't</button>
    </div>
  );
}

Konsolda:

text
fokusda: Lag'mon
Map'da: 3 ta

Ekranda:

text
Osh
Lag'mon
Manti
Lag'mon'ga o't

Har <li> o'z ref callback'ini oladi — u d.id ni closure orqali biladi. Qator olib tashlansa, cleanup uni Map'dan o'chiradi. Map'da eskirgan, DOM'da yo'q element qolmaydi. Cleanup bo'lmaganda Map o'chirilgan qatorlarning elementlarini abadiy saqlab qolardi (xotira oqishi).

Tekshirib ko'ring: useRef(new Map()) — new Map() har chizishda yaratiladimi? Bu muammomi?

Javob

Ha, new Map() ifodasi har chizishda bajariladi, lekin useRef faqat birinchisini oladi — qolganlari tashlanadi. Kichik Map uchun bu sezilmaydi. Qimmat obyekt bo'lsa — useRef I darsidagi lazy init (dangasa boshlash) (if (ref.current === null) ref.current = …).

5. ref — oddiy prop (React 19)

5.1 O'z komponentingizga ref

Siz SearchInput komponenti yasadingiz — ichida <input> va yorliq. Ota undagi maydonga fokus bermoqchi: <SearchInput ref={inputRef} />. React 19 da ref funksiya komponent uchun oddiy prop — uni boshqa props kabi olib, ichki elementga uzatasiz:

jsx
import { useRef } from "react";

function SearchInput({ ref, label }) {
  return (
    <label>
      {label} <input ref={ref} />
    </label>
  );
}

function App() {
  const inputRef = useRef(null);
  function search() {
    inputRef.current.focus();
    console.log("fokusda:", document.activeElement.tagName);
  }
  return (
    <div>
      <SearchInput ref={inputRef} label="Taom nomi" />
      <button onClick={search}>Qidirish</button>
    </div>
  );
}

Konsolda:

text
fokusda: INPUT

Ota SearchInput ning ichini bilmaydi. U faqat "menga sening maydoningning ref'ini ber" deydi, komponent esa qaysi elementni berishni o'zi hal qiladi.

5.2 Eski usul: forwardRef

React 18 va undan oldin funksiya komponent ref ni props'da olmasdi. React uni "yutib yuborardi" va ogohlantirardi. Buning uchun maxsus o'ram bor edi — forwardRef:

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

// Eski kodda shunday yozilgan (React 18 uslubi)
const OldInput = forwardRef(function OldInput(props, ref) {
  return <input ref={ref} {...props} />;
});

function App() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
    console.log("fokusda:", document.activeElement.placeholder);
  }, []);
  return <OldInput ref={inputRef} placeholder="Ism" />;
}

Konsolda:

text
fokusda: Ism

React 19.3 da forwardRef hali ishlaydi va ogohlantirish bermaydi. React hujjati esa uni "kelajakda eskiradi" deb belgilagan va yangi kodda ref prop'ni tavsiya qiladi. Eski loyihalarda, UI kutubxonalarida (Radix, MUI'ning eski versiyalari) forwardRef ni ko'p uchratasiz — endi uni o'qiy olasiz.

Ota komponent bolaning DOM elementini emas, faqat tanlangan metodlarini ko'rishi kerak bo'lsa (open(), reset()) — buning uchun useImperativeHandle bor (useImperativeHandle darsida).

5.3 TypeScript bilan

useRef<HTMLInputElement>(null) ning turi — RefObject<HTMLInputElement | null>. Prop sifatida ref ning turi — Ref<HTMLInputElement> (u obyekt ref'ini ham, callback'ni ham qamraydi):

tsx
import { useRef } from "react";
import type { Ref } from "react";

type Props = {
  ref?: Ref<HTMLInputElement>; // React 19: ref — oddiy prop
  label: string;
};

function SearchInput({ ref, label }: Props) {
  return <input ref={ref} aria-label={label} />;
}

export default function App() {
  const inputRef = useRef<HTMLInputElement>(null);
  return (
    <div>
      <SearchInput ref={inputRef} label="Qidiruv" />
      <button onClick={() => inputRef.current?.focus()}>Fokus</button>
    </div>
  );
}

Ekranda:

text
Fokus

inputRef.current?.focus() — ?. shart: tur HTMLInputElement | null, TypeScript null bo'lishi mumkinligini biladi (React + TS II darsida hooklar turlari batafsil).

6. Vazifalar qadami: fokus.ts o'rniga ref'lar

6.1 Nima o'zgaradi

refactor/reflar branch'ida useRef I darsidagi xo'jalik ref'lari tayyor. Bugun — DOM ref'lari va commit. fokus.ts (81 qator, getElementById va closest bilan) butunlay o'chadi:

Joy Holat 31 Holat 34
qator tugmalari (fokus) getElementById + [data-amal] ref Map: tugmaRef(id, amal)
tahrir maydoni tahrirgaOt() — id bilan TahrirQatori: ref + effekt
o'chirish dialogi dialogniOch() — id bilan OchirishDialog ga ref prop
eksport matni getElementById("eksport-matn") useRef<HTMLTextAreaElement>
qo'shni qator DOM'dagi nextElementSibling qoshniId(korinadigan, id) — sof funksiya

6.2 Tugmalar — ref Map va React 19 cleanup

Kanondagi ref callback yasovchi (aynan):

tsx
  // Ref callback: React elementni qo'yganda chaqiradi, qaytargan
  // funksiyasini — element olib tashlanganda (React 19: ref cleanup)
  function tugmaRef(id: number, amal: FokusAmali) {
    return (element: HTMLElement | null) => {
      if (element === null) {
        return undefined;
      }
      const kalit = `${id}:${amal}`;
      tugmalar.current.set(kalit, element);
      return () => {
        tugmalar.current.delete(kalit);
      };
    };
  }

«Ro'yxat uchun ref Map» dagi g'oya: tugmalar — useRef(new Map<string, HTMLElement>()), kalit — "5:ochir" kabi. tugmaRef(id, amal) ref callback qaytaradi — har qatorda ref={tugmaRef(vazifa.id, "almashtir")} deb ishlatiladi. Fokus berish endi DOM qidiruvi emas, Map'dan olish:

tsx
    (
      topilgan(id) ??
      topilgan(zaxiraId) ??
      sarlavhaRef.current
    )?.focus();

Avval o'sha qator, bo'lmasa qo'shnisi, u ham bo'lmasa — "Ro'yxat" sarlavhasi (sarlavhaRef). Bobur hech qachon sahifa boshiga uloqtirilmaydi.

6.3 Dialog va AmalTugmasi — ref prop

OchirishDialog va AmalTugmasi — o'z komponentlarimiz. Ota ularning ichidagi <dialog> va <button> ga ref uzatadi — React 19 usulida (kanon):

diff
 type Props = {
+  ref: Ref<HTMLDialogElement>; // React 19: ref — oddiy prop
   matn: string | null; // o'chirilayotgan vazifa matni
   onJavob: (tasdiq: boolean) => void;
 };

-export function OchirishDialog({ matn, onJavob }: Props) {
+export function OchirishDialog({ ref, matn, onJavob }: Props) {
   return (
     <dialog
+      ref={ref}

App da dialog dialogRef.current.showModal() bilan ochiladi — fokus.ts dagi dialogniOch() o'rniga. forwardRef hech qayerda yo'q.

6.4 React Compiler qoidasi topgan tartib

fondaYangila (boshqa tabdan kelgan yangilanishda fokusni saqlash) endi App ichidagi funksiya bo'ldi — ref'larni ishlatadi. Uni effekt hodisasidan keyin e'lon qilganimizda ESLint (plagin 7, React Compiler qoidalari) to'xtatdi (haqiqiy):

text
`fondaYangila` accessed before it is declared  react-hooks/immutability

"fondaYangila e'lon qilinishidan oldin murojaat qilingan." Funksiya e'loni ko'tariladi (hoisting — Funksiya e'lon qilish va chaqirish) va JavaScript'da bu ishlardi. Lekin kompilyator qoidasi komponent ichida yuqoridan pastga tartibni talab qiladi. Funksiya effekt hodisasidan oldinga ko'chirildi.

6.5 Commit va tekshiruvlar

Commit (kanon): refactor: taymer va sinxron xo'jaligi useRef, fokus — ref Map. Body: elonTaymeri, { pult, kutilmoqda } — useRef; fokus.ts o'chirildi: tugmalar — ref Map (tugmaRef, ref cleanup), dialog — ref prop, TahrirQatori fokus+belgilash — ref + effekt; qoshniId — sof (holat.ts).

  • npm test — node:test 148 (qoshniId uchun +1), Vitest 23. Lint, format, tip — exit 0.
  • Brauzer tekshiruvi — 159/159. Undagi klaviatura ssenariylari (o'chirgandan keyin fokus qo'shni qatorga, Esc bilan tahrirdan chiqish) — o'sha natija, endi ref'lar bilan.
  • getElementById faqat main.tsx da qoldi — React ildizini topish uchun.
  • Diff: 14 fayl, +266 −201. Build: index-…js 248.58 kB (gzip 78.86).

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Cannot read properties of null (reading 'focus') ref chizish paytida yoki element hali yo'q effekt yoki ishlovchida; ?.
fokus kutilgan joyda emas focus() state yangilanishidan oldin chaqirildi effekt yoki flushSync dan keyin
TS2322: … is not assignable to type 'Ref<…>' ref callback qiymat qaytardi { … } jingalak qavs
Map'da o'chgan elementlar ref callback cleanup'siz return () => map.delete(id)
ota'dagi ref null (React 18 kodi) funksiya komponent ref ni uzatmadi React 19: ref prop; 18: forwardRef
React chizgan elementni remove() qildingiz DOM'ni qo'lda o'zgartirish state orqali

8. Mashqlar

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

1-mashq (oson): qachon null?

Kodni ishga tushirmasdan, konsol qatorlarini to'ldiring.

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

function App() {
  const ref = useRef(null);
  console.log("A:", ref.current === null);
  useEffect(() => {
    console.log("B:", ref.current === null);
  }, []);
  const check = () => console.log("C:", ref.current.tagName);
  return (
    <button ref={ref} onClick={check}>
      Tekshir
    </button>
  );
}
  • A qatori:
  • B qatori:
  • C qatori:
Yechim

Konsolda:

text
A: true
B: false
C: BUTTON

A — birinchi chizish, element hali yo'q. B — effekt commit'dan keyin, ref to'lgan. C — bosish, element joyida.

2-mashq (o'rta): xatoda fokus

Bron formasi: ism bo'sh yuborilsa, xato matni chiqadi — lekin fokus tugmada qoladi. Bobur qayerga qaytishni bilmaydi. Ref bilan tuzating: xato bo'lsa, fokus ism maydoniga o'tsin. Konsolga fokusdagi element id sini yozing. Ishora: «Fokus tugmasi» bo'limi; fokus setError dan keyin, o'sha ishlovchida.

jsx
import { useState } from "react";

function App() {
  const [error, setError] = useState("");
  function submit(e) {
    e.preventDefault();
    const name = new FormData(e.currentTarget).get("name");
    if (name.trim() === "") {
      setError("Ismni kiriting");
    }
    console.log("fokusda:", document.activeElement.id);
  }
  return (
    <form onSubmit={submit}>
      <input id="ism" name="name" aria-label="Ism" />
      <p role="alert">{error}</p>
      <button id="bron">Bron qilish</button>
    </form>
  );
}
Yechim
jsx
import { useRef, useState } from "react";

function App() {
  const [error, setError] = useState("");
  const nameRef = useRef(null);
  function submit(e) {
    e.preventDefault();
    const name = new FormData(e.currentTarget).get("name");
    if (name.trim() === "") {
      setError("Ismni kiriting");
      nameRef.current.focus(); // ✅ xato joyiga
    }
    console.log("fokusda:", document.activeElement.id);
  }
  return (
    <form onSubmit={submit}>
      <input id="ism" ref={nameRef} name="name" aria-label="Ism" />
      <p role="alert">{error}</p>
      <button id="bron">Bron qilish</button>
    </form>
  );
}

Konsolda:

text
fokusda: ism

Ekranda:

text
Ismni kiriting
Bron qilish

Fokus maydonda — Bobur darhol yozishi, Malika esa role="alert" tufayli xatoni eshitishi mumkin. vazifalar dagi YangiVazifaForma aynan shunday qiladi (maydonRef).

3-mashq (qiyin): o'chirgandan keyin fokus

Ro'yxatdagi har taomning «O'chirish» tugmasi bor. Taom o'chirilganda fokus keyingi taomning «O'chirish» tugmasiga o'tishi kerak (oxirgisi o'chsa — oldingisiga). Hozir fokus yo'qolib, BODY ga tushadi. Ref Map va flushSync bilan tuzating:

  • buttons — useRef(new Map()), ref callback cleanup bilan;
  • o'chirishdan oldin qo'shni id'ni hisoblang;
  • flushSync(() => setDishes(…)) — DOM darhol yangilansin (State II: flushSync — vazifalar da ko'p ishlatilgan), keyin fokus.

Konsolga fokusdagi tugmaning aria-label ini yozing.

jsx
import { useState } from "react";

const START = [
  { id: 1, name: "Osh" },
  { id: 2, name: "Lag'mon" },
  { id: 3, name: "Manti" },
];

function App() {
  const [dishes, setDishes] = useState(START);
  function remove(id) {
    setDishes(dishes.filter((d) => d.id !== id));
    console.log("fokusda:", document.activeElement.tagName);
  }
  return (
    <ul>
      {dishes.map((d) => (
        <li key={d.id}>
          {d.name}{" "}
          <button
            aria-label={`O'chirish: ${d.name}`}
            onClick={() => remove(d.id)}
          >
            O'chirish
          </button>
        </li>
      ))}
    </ul>
  );
}
Yechim
jsx
import { useRef, useState } from "react";
import { flushSync } from "react-dom";

const START = [
  { id: 1, name: "Osh" },
  { id: 2, name: "Lag'mon" },
  { id: 3, name: "Manti" },
];

function App() {
  const [dishes, setDishes] = useState(START);
  const buttons = useRef(new Map()); // id → <button>
  function remove(id) {
    const i = dishes.findIndex((d) => d.id === id);
    const next = dishes[i + 1] ?? dishes[i - 1]; // qo'shni yoki yo'q
    flushSync(() => setDishes(dishes.filter((d) => d.id !== id)));
    if (next) buttons.current.get(next.id)?.focus();
    const label = document.activeElement.getAttribute("aria-label");
    console.log("fokusda:", label);
  }
  return (
    <ul>
      {dishes.map((d) => (
        <li key={d.id}>
          {d.name}{" "}
          <button
            ref={(el) => {
              buttons.current.set(d.id, el);
              return () => buttons.current.delete(d.id);
            }}
            aria-label={`O'chirish: ${d.name}`}
            onClick={() => remove(d.id)}
          >
            O'chirish
          </button>
        </li>
      ))}
    </ul>
  );
}

Konsolda:

text
fokusda: O'chirish: Manti

Ekranda:

text
Osh O'chirish
Manti O'chirish

Qo'shni o'chirishdan oldin hisoblandi — keyin u ro'yxatda bo'lmasdi. flushSync dan keyin DOM yangilangan: Lag'mon qatori yo'q va uning ref cleanup'i Map'dan o'chirgan. Manti tugmasi esa joyida. Bu vazifalar dagi fokusla(zaxira, "ochir", null) ning kichik nusxasi.

4-mashq: Vazifalar qadami

refactor/reflar branch'ida «Vazifalar qadami: fokus.ts o'rniga ref'lar» bo'limidagi o'zgarishlarni kiriting, fokus.ts ni o'chiring, commit qiling va PR oching.

Yechim

Tekshiruv ro'yxati:

  • grep -rn "getElementById" src/ — faqat main.tsx (ildiz).
  • grep -rn "forwardRef" src/ — bo'sh: ref — oddiy prop.
  • npm run lint — react-hooks/immutability yoki react-hooks/refs xatosi yo'q.
  • Klaviatura bilan sinang: vazifani o'chiring — fokus qo'shni qatorning «O'chirish» tugmasida; tahrirlashni boshlang — matn belgilangan; Esc — fokus «Tahrirlash» ga qaytadi.
  • npm test — node 148, Vitest 23; brauzer tekshiruvi — 159/159.

Commit — refactor: taymer va sinxron xo'jaligi useRef, fokus — ref Map. PR'ni birlashtiring.

9. Real ishda

  • Fokus boshqaruvi — accessibility'ning asosi: modal ochilganda fokus ichiga, yopilganda tugmaga qaytishi; xatoda — maydonga (Fokus boshqaruvi va klaviatura navigatsiyasi). Ref'siz buni toza yozib bo'lmaydi.
  • Uchinchi tomon kutubxonalari (Leaflet xaritasi, Chart.js, video pleyer) DOM elementini talab qiladi — kutubxona nusxasi effekt ichida yaratiladi (masalan, Leaflet'da L.map(ref.current)), remove() yoki destroy() — cleanup'da (Xaritalar I).
  • UI kutubxonalari (shadcn/ui, Radix) React 19 ga o'tishda forwardRef ni olib tashlab, ref prop'ga o'tmoqda. Eski versiyalarda uni ko'p ko'rasiz.
  • Intervyu savollari: "DOM elementiga qanday murojaat qilinadi?", "ref callback nima va React 19 da nima o'zgardi?", "forwardRef nima uchun kerak edi va hozir-chi?".

Xulosa

  • ref={myRef} — React commit paytida DOM elementini myRef.current ga qo'yadi, olib tashlanganda null qiladi; chizish paytida u hali null.
  • Ref bilan — React bajarmaydigan buyruqlar: focus(), select(), scrollIntoView(), showModal(), o'lcham. DOM tuzilmasini qo'lda o'zgartirmang.
  • Ref callback — ref={(el) => …}; React 19 da u cleanup qaytaradi (StrictMode uni ham ikki marta sinaydi). TS'da qiymat qaytarish — TS2322.
  • Ro'yxat uchun — bitta ref ichida Map, set va cleanup'da delete.
  • React 19: ref — funksiya komponent uchun oddiy prop; forwardRef — eski kodda.
  • vazifalar: fokus.ts o'chdi — ref Map, ref prop, ref + effekt; 159/159.

Keyingi dars: useImperativeHandle va imperativ API — komponent ota'ga DOM elementini emas, faqat tanlangan metodlarni (open, reset) ochib berishi.

Manbalar

  • React: "Manipulating the DOM with Refs" — react.dev/learn/manipulating-the-dom-with-refs
  • React: ref callback va cleanup — react.dev/reference/react-dom/components/common#ref-callback
  • React blogi: "React v19" (2024-12-05) — "ref as a prop", "Cleanup functions for refs" — react.dev/blog/2024/12/05/react-19
  • React: forwardRef — react.dev/reference/react/forwardRef
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
useRef II: DOM elementiga ref, fokus va React 19 dagi ref prop — IlmHamroh