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

React useImperativeHandle: komponentning imperativ API'si — ota faqat kerakli metodlarni oladi

Qisqacha: ref prop orqali ota bola komponentning butun DOM elementini oladi va u bilan xohlaganini qila oladi. useImperativeHandle(ref, () => ({ focus, clear }), []) bilan bola o'rniga o'zi tanlagan kichik obyektni beradi — ota faqat shu metodlarni chaqira oladi. Bu fokus, aylantirish (scroll), matnni belgilash kabi faqat buyruq bilan bajariladigan ishlar uchun. Ochiq/yopiq kabi narsalarni esa props bilan boshqaring.

Bu darsda

  • useImperativeHandle nima uchun kerakligini va ref prop'dan farqini tushuntira olasiz.
  • Bola komponentdan otaga focus(), clear(), reset() kabi cheklangan API berasiz.
  • Handle'dagi eskirgan closure (stale closure) tuzog'ini bog'liqliklar bilan tuzatasiz.
  • TypeScript'da handle turini Ref<T> bilan yozasiz.
  • Qachon imperativ API, qachon props kerakligini ajrata olasiz.

Oldin bilishingiz kerak: useRef II: DOM elementlari va ref prop, useRef I: render chaqirmaydigan xotira, useEffect II: dependency massivi.

1. Nega bu kerak?

Oldingi darsda ref ni oddiy prop sifatida bola komponentga berdik. Bola uni <input ref={ref}> ga ulasa, ota haqiqiy DOM elementini oladi. Masalan, ota ref.current.focus() deb fokus qo'yadi.

«Bahor» menyu sahifasida SearchBox komponenti bor: qidiruv maydoni va tozalash tugmasi. Jasur aka so'radi: "Sahifadagi «Qidiruvga o'tish» tugmasini bossam, kursor qidiruvga tushsin." Sardor ref ni to'g'ridan-to'g'ri <input> ga uladi. Ishladi.

Bir hafta o'tib boshqa dasturchi ota komponentda shunday yozdi: search.current.value = "osh". Ekranda "osh" paydo bo'ldi, lekin SearchBox ning state'i bo'sh qoldi. Natija — qidiruv ishlamadi, chunki React bu o'zgarishdan bexabar. Muammo shundaki, ota butun <input> ni oldi: value, style, remove() — hammasini.

Hayotiy o'xshatish: oshxonaning tarqatish darchasi. Ofitsiant darchadan buyurtma beradi va tayyor taomni oladi. Unga oshxona kalitini bermaysiz — ichkariga kirib, qozonni o'zi aralashtirmasin. useImperativeHandle — shunday darcha. Bola komponent otaga faqat o'zi tanlagan "tugmalarni" ko'rsatadi.

2. Imperativ va deklarativ: eslatma

2.1 Brauzerdagi buyruqlar

React nima va nega darsida ikki uslubni ko'rgan edik. Deklarativ — "ekran qanday bo'lsin" deysiz: <p>{count}</p>. Imperativ — "nima qil" deysiz: p.textContent = count.

React'da deyarli hamma narsa deklarativ: matn, rang, ko'rinish — props va state'dan. Lekin brauzerda ba'zi narsalar faqat buyruq bilan bajariladi. Ularning atributi yo'q, faqat metodi bor:

Ish DOM metodi Atribut bilan bo'ladimi?
Fokus qo'yish input.focus() yo'q
Matnni belgilash input.select() yo'q
Elementgacha aylantirish el.scrollIntoView() yo'q
Video ijrosi video.play() yo'q
Modal dialog dialog.showModal() yo'q

Vanilla JavaScript'da bularni document.querySelector(...) bilan topib chaqirardingiz. React'da element ref orqali olinadi (useRef II).

2.2 ref prop bilan butun element ketadi

Bola ref ni <input> ga ulasa, otaga haqiqiy HTMLInputElement keladi. Uning yuzlab xususiyati va metodi bor. Ota ulardan istaganini ishlata oladi — jumladan, React boshqarayotgan narsalarni ham. Bu bola komponentning "ichki ishi" ochilib qolgani demak. Keyinchalik bola o'zgarsa (masalan, <input> o'rniga <textarea> qo'yilsa), otadagi kod jim buziladi.

Tekshirib ko'ring: Ota search.current.value = "osh" deb yozdi, SearchBox esa controlled (Formalar I): value={query}. Nega qidiruv ishlamaydi?

Javob

Controlled maydonda "haqiqat" — state (query). Ota DOM'ga to'g'ridan-to'g'ri yozdi, onChange chaqirilmadi, query bo'sh qoldi. Qidiruv natijasi state'dan hisoblanadi — u o'zgarmadi. Keyingi renderda React esa maydonga yana query ni yozadi va "osh" yo'qolishi mumkin.

3. useImperativeHandle sintaksisi

3.1 Birinchi misol

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

function SearchBox({ ref }) {
  const input = useRef(null); // ichki ref — faqat bolaniki
  const [query, setQuery] = useState("");

  useImperativeHandle(ref, () => ({
    focus() {
      input.current.focus();
    },
    clear() {
      setQuery("");
    },
  }), []);

  return (
    <input
      ref={input}
      aria-label="Qidiruv"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
    />
  );
}

function App() {
  const search = useRef(null);
  console.log("render:", search.current);
  useEffect(() => {
    console.log("effekt:", Object.keys(search.current));
  }, []);

  function goToSearch() {
    search.current.focus();
    const active = document.activeElement;
    console.log("fokusda:", active.getAttribute("aria-label"));
  }

  return (
    <div>
      <SearchBox ref={search} />
      <button onClick={goToSearch}>Qidiruvga o'tish</button>
    </div>
  );
}

Konsolda:

text
render: null
effekt: (2) ['focus', 'clear']
fokusda: Qidiruv

Ekranda:

text
Qidiruvga o'tish

Uchta argument:

  1. ref — otadan kelgan ref (React 19 da oddiy prop, useRef II).
  2. Yaratuvchi funksiya — otaga beriladigan obyektni qaytaradi. Bu obyekt handle ("dastak", ushlagich) deb ataladi.
  3. Bog'liqliklar massivi — useEffect dagidek: qachon handle qayta yaratilsin.

Endi search.current — <input> emas, ikki metodli oddiy obyekt. Konsoldagi (2) ['focus', 'clear'] shuni ko'rsatyapti. Ota value ni ham, style ni ham ko'rmaydi.

SearchBox ichida ikkita ref bor. input — bolaning o'z ref'i, haqiqiy <input> ga ulangan. ref — otaniki, unga React handle'ni qo'yadi. Bola ichkarida DOM bilan bemalol ishlaydi, tashqariga esa faqat darchani ochadi.

3.2 Handle qachon paydo bo'ladi

Konsolning birinchi qatori render: null. Render paytida handle hali yo'q — React uni DOM o'zgarishlarini qo'llagandan keyin (commit bosqichida) qo'yadi. Bu oddiy DOM ref'idagi bilan bir xil qoida (useRef II).

Qadam search.current Ekranda
App birinchi render null hali hech narsa
commit: DOM tayyor, handle qo'yildi {focus, clear} maydon va tugma
effektlar {focus, clear} o'zgarishsiz
«Qidiruvga o'tish» bosildi {focus, clear} kursor qidiruvda
SearchBox olib tashlandi null maydon yo'q

Oxirgi qatorni tekshiramiz. Komponent ekrandan olib tashlansa, React ref'ga yana null yozadi:

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

function SearchBox({ ref }) {
  useImperativeHandle(ref, () => {
    console.log("handle yaratildi");
    return { focus() {} };
  }, []);
  return <input aria-label="Qidiruv" />;
}

function App() {
  const search = useRef(null);
  const [shown, setShown] = useState(true);

  function remove() {
    setShown(false);
    setTimeout(() => console.log("keyin:", search.current), 0);
  }

  return (
    <div>
      {shown && <SearchBox ref={search} />}
      <button onClick={remove}>Olib tashlash</button>
    </div>
  );
}

Konsolda:

text
handle yaratildi
keyin: null

Ekranda:

text
Olib tashlash

Shuning uchun handle'ni hodisa ishlovchisida yoki effektda chaqiring — render ichida emas. Render paytida u null bo'ladi va null.focus() xato beradi (pastda, «Ko'p uchraydigan xatolar» da).

Tekshirib ko'ring: useImperativeHandle ning yaratuvchi funksiyasi { focus() {...} } qaytarmay, input.current ni qaytarsa nima bo'ladi?

Javob

Ota yana butun <input> ni oladi — xuddi ref ni to'g'ridan-to'g'ri <input> ga ulagandek. Hook ishlaydi, lekin uning foydasi yo'qoladi: darcha o'rniga eshik ochildi. Handle'ga faqat otaga kerak bo'lgan metodlarni qo'ying.

4. Handle — faqat DOM emas

4.1 Bir nechta element va state

Handle metodlari ichida istalgan kod bo'lishi mumkin: bir nechta ref, state yangilash, hisob-kitob. «Bahor» kassasida buyurtma formasi bor. Kassir «Yangi buyurtma» ni bossa, forma tozalanishi va kursor ism maydoniga tushishi kerak:

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

function OrderForm({ ref }) {
  const nameInput = useRef(null);
  const [name, setName] = useState("");
  const [qty, setQty] = useState("1");

  useImperativeHandle(ref, () => ({
    reset() {
      setName("");
      setQty("1");
      nameInput.current.focus();
    },
  }), []);

  return (
    <div>
      <p>
        <label>
          Ism
          <input ref={nameInput} value={name}
            onChange={(e) => setName(e.target.value)} />
        </label>
      </p>
      <p>
        <label>
          Soni
          <input value={qty}
            onChange={(e) => setQty(e.target.value)} />
        </label>
      </p>
      <p>Buyurtma: {name || "—"}, {qty} ta osh</p>
    </div>
  );
}

function App() {
  const form = useRef(null);
  return (
    <section>
      <OrderForm ref={form} />
      <button onClick={() => form.current.reset()}>
        Yangi buyurtma
      </button>
    </section>
  );
}

Ekranda:

text
Ism
Soni
Buyurtma: —, 1 ta osh
Yangi buyurtma

Harakat qatori avval ikki maydonni to'ldirdi ("Ali", 3), keyin «Yangi buyurtma» ni bosdi. reset() ikkala state'ni ham tikladi va fokusni qo'ydi. Ota formaning ichida nechta maydon borligini ham, ular qanday state'da saqlanishini ham bilmaydi. U faqat bitta so'zni biladi: reset.

Bu — cheklangan API (application programming interface — "dasturlash interfeysi", ya'ni boshqa kod sizning kodingizdan foydalanadigan eshiklar ro'yxati). Bola komponentning ichi keyin o'zgarishi mumkin: maydonlar ko'payadi, state reducer'ga ko'chadi. Ota kodi esa o'zgarmaydi — reset() o'sha-o'sha.

4.2 Handle stateni o'qisa: eskirgan qiymat

Yangi talab: ota izoh matnini o'qiy olsin. Sardor getText() qo'shdi:

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

function NoteBox({ ref }) {
  const [text, setText] = useState("");
  useImperativeHandle(ref, () => ({
    getText: () => text,
  }), []); // ❌ text bog'liqliklarda yo'q
  return (
    <input aria-label="Izoh" value={text}
      onChange={(e) => setText(e.target.value)} />
  );
}

function App() {
  const note = useRef(null);
  function read() {
    console.log(JSON.stringify(note.current.getText()));
  }
  return (
    <div>
      <NoteBox ref={note} />
      <button onClick={read}>O'qish</button>
    </div>
  );
}

Konsolda:

text
""

Ekranda:

text
O'qish

Maydonda "Piyozsiz" turibdi, getText() esa bo'sh satr qaytardi. Sabab tanish — eskirgan closure (stale closure, useEffect II darsida ko'rgansiz). Handle birinchi renderda yaratildi va o'sha renderdagi text ni ("") eslab qoldi. Bog'liqliklar [] — React handle'ni boshqa hech qachon yangilamadi.

Qadam text (state) handle eslagan text
birinchi render "" ""
"Piyozsiz" yozildi, qayta render "Piyozsiz" "" (yangilanmadi)
«O'qish» "Piyozsiz" "" → konsolda ""

Tuzatish — text ni bog'liqliklarga qo'shish:

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

function NoteBox({ ref }) {
  const [text, setText] = useState("");
  useImperativeHandle(ref, () => ({
    getText: () => text,
  }), [text]); // ✅ text o'zgarsa — handle yangisi
  return (
    <input aria-label="Izoh" value={text}
      onChange={(e) => setText(e.target.value)} />
  );
}

function App() {
  const note = useRef(null);
  function read() {
    console.log(JSON.stringify(note.current.getText()));
  }
  return (
    <div>
      <NoteBox ref={note} />
      <button onClick={read}>O'qish</button>
    </div>
  );
}

Konsolda:

text
"Piyozsiz"

Ekranda:

text
O'qish

Endi har harfda React eski handle'ni olib, yangisini qo'yadi. Bu xatoni linter oldindan aytadi. eslint-plugin-react-hooks 7.1.1 ning exhaustive-deps qoidasi useImperativeHandle ni ham tekshiradi (Hooklar qoidalari). Birinchi variantni npx eslint bilan tekshirdik:

text
  7:7  warning  React Hook useImperativeHandle has a missing dependency: 'text'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

"useImperativeHandle da text bog'liqligi yetishmayapti. Uni qo'shing yoki bog'liqliklar massivini olib tashlang." vazifalar da lint skripti --max-warnings=0 bilan ishlaydi — bunday ogohlantirish commit'ga o'tmaydi.

Bog'liqliklar massivini umuman yozmasa ham bo'ladi — unda handle har renderda qayta yaratiladi. Bu xato emas, faqat ortiqcha ish. focus() va reset() kabi faqat ref va setter ishlatadigan metodlar uchun [] to'g'ri: ref obyekti va setX funksiyalari renderlar orasida o'zgarmaydi.

Maslahat: Ko'pincha ota bola state'ini o'qishi shart emas. Agar shunday ehtiyoj tug'ilsa, avval o'ylang: balki bu state otada bo'lishi kerakdir (State'ni ko'tarish)? Bola onChange bilan xabar beradi, ota esa o'zi saqlaydi.

5. TypeScript'da handle turi

5.1 Ref<T> — handle turi

Handle — oddiy obyekt, uning turini o'zingiz yozasiz. Bola ref prop'ini Ref<SearchHandle> deb e'lon qiladi, ota esa useRef<SearchHandle>(null):

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

export type SearchHandle = {
  focus: () => void;
};

function SearchBox({ ref }: { ref?: Ref<SearchHandle> }) {
  const input = useRef<HTMLInputElement>(null);
  useImperativeHandle(ref, () => ({
    focus() {
      input.current?.focus();
    },
  }), []);
  return <input ref={input} aria-label="Qidiruv" />;
}

export default function App() {
  const search = useRef<SearchHandle>(null);
  return (
    <div>
      <SearchBox ref={search} />
      <button onClick={() => search.current?.focus()}>
        Qidiruvga o'tish
      </button>
    </div>
  );
}

Ekranda:

text
Qidiruvga o'tish

ref?: — ixtiyoriy: ota ref bermasa ham SearchBox ishlaydi. useImperativeHandle ref ning undefined bo'lishini o'zi ko'taradi. ?. (?? va ?.) — current null bo'lishi mumkinligini TypeScript eslatadi.

5.2 TypeScript ortiqcha so'rovni ushlaydi

Ota endi qidiruv maydonining rangini o'zgartirmoqchi:

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

type SearchHandle = { focus: () => void };

function SearchBox({ ref }: { ref?: Ref<SearchHandle> }) {
  const input = useRef<HTMLInputElement>(null);
  useImperativeHandle(ref, () => ({
    focus() { input.current?.focus(); },
  }), []);
  return <input ref={input} aria-label="Qidiruv" />;
}

export default function App() {
  const search = useRef<SearchHandle>(null);
  function paint() {
    search.current!.style.color = "red";
  }
  return <SearchBox ref={search} />;
}

Xato:

text
TS2339: Property 'style' does not exist on type 'SearchHandle'.

"SearchHandle turida style xususiyati yo'q." Darcha qisqa — ota faqat focus ni ko'radi. TypeScript'siz esa xato ishlash paytida chiqadi:

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

function SearchBox({ ref }) {
  const input = useRef(null);
  useImperativeHandle(ref, () => ({
    focus() { input.current.focus(); },
  }), []);
  return <input ref={input} aria-label="Qidiruv" />;
}

function App() {
  const search = useRef(null);
  return (
    <div>
      <SearchBox ref={search} />
      <button onClick={() => { search.current.style.color = "red"; }}>
        Qizil
      </button>
    </div>
  );
}

Konsolda:

text
Uncaught TypeError: Cannot set properties of undefined (setting 'color')
TypeError: Cannot set properties of undefined (setting 'color')

"undefined ning xususiyatlarini o'rnatib bo'lmaydi (color ni o'rnatishda)." search.current.style — undefined, chunki handle'da style yo'q. Xato hodisa ishlovchisida chiqdi, shuning uchun ekran buzilmadi — faqat tugma ishlamadi. Bu — biz xohlagan cheklov: ota bolaning ichiga kira olmaydi.

6. Eski kod: forwardRef

forwardRef ning o'zini useRef II darsida ko'rgansiz: React 19 gacha funksiya komponent ref ni props'da ololmasdi, u o'ram orqali ikkinchi argument bo'lib kelardi. Eski loyihalar va kutubxonalarda useImperativeHandle aynan shu o'ram bilan birga uchraydi. Farq faqat ref qayerdan kelishida — hookning o'zi bir xil:

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

// React 18 uslubi: ref — ikkinchi argument
const OldSearch = forwardRef(function OldSearch(props, ref) {
  const input = useRef(null);
  useImperativeHandle(ref, () => ({
    focus() { input.current.focus(); },
  }), []);
  return <input ref={input} aria-label="Eski qidiruv" />;
});

function App() {
  const search = useRef(null);
  function go() {
    search.current.focus();
    console.log("handle:", Object.keys(search.current).join(", "));
  }
  return (
    <div>
      <OldSearch ref={search} />
      <button onClick={go}>Fokus</button>
    </div>
  );
}

Konsolda:

text
handle: focus

Ota baribir <input> ni emas, faqat focus ni oldi. Eski kodni yangilashda ikki qadam yetadi: forwardRef(...) o'ramini olib tashlang va ref ni props'dan oling (function OldSearch({ ref })). React 19 e'loni (react.dev blog, 2024-12-05) forwardRef kelajakda eskirgan deb belgilanishini aytadi.

7. Qachon props afzalroq

7.1 Imperativ API — oxirgi chora

react.dev'dagi useImperativeHandle sahifasi aniq qoida beradi: ref'lardan faqat props bilan ifodalab bo'lmaydigan imperativ ishlar uchun foydalaning — elementgacha aylantirish, fokus, animatsiyani boshlash, matnni belgilash.

Sardor modal oyna yasadi va otaga open(), close() metodlarini berdi:

jsx
// ❌ ochiq/yopiq — bolaning yashirin state'i
modal.current.open();

Bu ishlaydi, lekin uch muammo bor:

  • Ota modal ochiqmi-yo'qmi bilmaydi. Sarlavhada "Modal ochiq" deb yozish uchun yana isOpen() metodi kerak bo'ladi.
  • Holat URL'dan yoki serverdan kelsa (masalan, ?bron=1 — bron oynasi ochiq), uni chizib bo'lmaydi: avval render, keyin effektda open().
  • Ikki joy bitta narsani boshqaradi — "yagona haqiqat manbai" (State'ni ko'tarish) buziladi.

Deklarativ variant — ochiqlik ota state'ida, bola uni prop sifatida oladi:

jsx
import { useState } from "react";

function BookingModal({ isOpen, onClose }) {
  if (!isOpen) return null;
  return (
    <div role="dialog" aria-label="Bron">
      <p>Stol bron qilish — 10:00 dan 22:00 gacha</p>
      <button onClick={onClose}>Yopish</button>
    </div>
  );
}

function App() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <main>
      <p>Modal: {isOpen ? "ochiq" : "yopiq"}</p>
      <button onClick={() => setIsOpen(true)}>
        Stol bron qilish
      </button>
      <BookingModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
      />
    </main>
  );
}

Ekranda:

text
Modal: yopiq
Stol bron qilish

Harakatlar oynani ochdi va yopdi — oxirida yana "yopiq". Ota har doim holatni biladi va uni sarlavhada ishlatadi. open() metodi kerak emas.

7.2 Qaror jadvali

Vazifa Yechim Nega
ochiq/yopiq, tanlangan tab, joriy slayd props (isOpen, index) bu — holat, ota ham bilishi kerak
fokus qo'yish, matnni belgilash handle (focus, select) brauzerda atributi yo'q
ro'yxat oxirigacha aylantirish handle (scrollToEnd) scrollIntoView — buyruq
video yoki audio ijrosi handle (play, pause) yoki prop DOM'da buyruq; holat ham bo'lishi mumkin
formani tozalash prop (key) yoki handle (reset) ikkalasi ham to'g'ri

Oxirgi qator haqida: formani butunlay yangidan boshlash uchun key ni o'zgartirish ham yetadi — React komponentni o'chirib, yangisini yaratadi (State'ning saqlanishi va key). Handle esa qo'shimcha ish kerak bo'lganda qulay: masalan, tozalashdan keyin fokus qo'yish.

Tekshirib ko'ring: «Bahor» menyusi karuselida (slayder) joriy taom URL'da saqlanadi: ?taom=3. Karuselga goTo(3) metodi berasizmi yoki index={3} prop'i?

Javob

index={3} prop'i. Joriy slayd — holat: u URL'dan keladi va boshqa joylar ham uni ko'rsatadi ("3 / 12"). Prop bilan karusel birinchi renderdanoq to'g'ri slaydda chiziladi. goTo(3) esa avval noto'g'ri slaydni chizib, keyin effektda o'tkazardi.

flowchart TD
  S["Ota boladan nimadir xohlaydi"] --> Q{"Bu holatmi?<br/>ochiq, tanlangan, qiymat"}
  Q -- "ha" --> P["props + onX"]
  Q -- "yo'q, buyruq" --> D{"Butun DOM kerakmi?"}
  D -- "faqat bitta element, kutubxona" --> R["ref prop"]
  D -- "yo'q, bir necha metod" --> H["useImperativeHandle"]

Diagramma qaror tartibini ko'rsatadi: avval "bu holatmi?" deb so'rang. Ko'p hollarda javob "ha" va masala props bilan yechiladi.

8. Ko'p uchraydigan xatolar

8.1 Render ichida handle'ni chaqirish

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

function SearchBox({ ref }) {
  useImperativeHandle(ref, () => ({ focus() {} }), []);
  return <input aria-label="Qidiruv" />;
}

function App() {
  const search = useRef(null);
  search.current.focus(); // ❌ renderda — handle hali yo'q
  return <SearchBox ref={search} />;
}

Konsolda:

text
Uncaught TypeError: Cannot read properties of null (reading 'focus')

"null ning xususiyatlarini o'qib bo'lmaydi (focus ni o'qishda)." Birinchi renderda search.current — null. Tuzatish: chaqiruvni hodisa ishlovchisiga yoki effektga ko'chiring.

8.2 Satr ko'rinishidagi ref

Juda eski kodda (class komponentlar davri) ref="input" deb yozilardi. React 19 bu shaklni butunlay olib tashladi:

jsx
function SearchBox({ ref }) {
  return <input ref={ref} aria-label="Qidiruv" />;
}

function App() {
  return <SearchBox ref="search" />;
}

Konsolda:

text
Uncaught Error: Expected ref to be a function, an object returned by React.createRef(), or undefined/null.

"Ref funksiya, React.createRef() qaytargan obyekt yoki undefined/null bo'lishi kutilgan edi." Tuzatish: useRef(null) yarating va o'shani bering.

8.3 Jamlanma

Belgi Sabab Davosi
Cannot read properties of null (reading 'focus') handle renderda chaqirildi hodisa yoki effektda chaqiring
handle eski qiymat qaytaradi bog'liqliklarda state yo'q [text] qo'shing
Cannot set properties of undefined handle'da yo'q xususiyat handle'ga metod qo'shing yoki props ishlating
ota bola state'ini o'zgartiradi butun DOM element ochilgan useImperativeHandle bilan cheklang
Expected ref to be a function… ref="satr" useRef

9. Mashqlar

Mashqlar kurs/mashqlar/17/35-imperativ-api/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): miqdor maydoni

QtyInput — taom soni maydoni. Ota faqat ikki narsa qila olsin: fokus qo'yish va matnni belgilash (select() — maydondagi butun matnni ajratadi, yangi son yozilsa eskisi o'chadi). Bo'sh joylarni to'ldiring:

jsx
function QtyInput({ ref }) {
  const input = useRef(null);
  [___:useImperativeHandle](ref, () => ({
    focus() { input.current.focus(); },
    select() { input.current.[___:select](); },
  }), [___:[]]);
  return <input ref={input} aria-label="Soni" defaultValue="1" />;
}
Yechim
jsx
import { useImperativeHandle, useRef } from "react";

function QtyInput({ ref }) {
  const input = useRef(null);
  useImperativeHandle(ref, () => ({
    focus() { input.current.focus(); },
    select() { input.current.select(); },
  }), []);
  return <input ref={input} aria-label="Soni" defaultValue="1" />;
}

function App() {
  const qty = useRef(null);
  function edit() {
    qty.current.focus();
    qty.current.select();
    console.log(Object.keys(qty.current));
  }
  return (
    <div>
      <QtyInput ref={qty} />
      <button onClick={edit}>Sonini o'zgartirish</button>
    </div>
  );
}

Konsolda:

text
(2) ['focus', 'select']

Ekranda:

text
Sonini o'zgartirish

Bog'liqliklar []: metodlar faqat ref ishlatadi, ref obyekti esa o'zgarmaydi.

2-mashq (o'rta): eskirgan izoh

Ofitsiant izoh yozadi, kassir «Chekka yozish» tugmasini bosadi. Chekda har doim bo'sh izoh chiqyapti. Xatoni toping va isEmpty() metodini ham qo'shing (bo'sh joylardan iborat matn ham bo'sh hisoblansin — trim(), matnni tozalash).

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

function NoteBox({ ref }) {
  const [text, setText] = useState("");
  useImperativeHandle(ref, () => ({ getText: () => text }), []);
  return (
    <input aria-label="Izoh" value={text}
      onChange={(e) => setText(e.target.value)} />
  );
}

function App() {
  const note = useRef(null);
  const print = () => console.log("Chek:", note.current.getText());
  return (
    <div>
      <NoteBox ref={note} />
      <button onClick={print}>Chekka yozish</button>
    </div>
  );
}

Konsolda:

text
Chek:
Yechim
jsx
import { useImperativeHandle, useRef, useState } from "react";

function NoteBox({ ref }) {
  const [text, setText] = useState("");
  useImperativeHandle(ref, () => ({
    getText: () => text,
    isEmpty: () => text.trim() === "",
  }), [text]);
  return (
    <input aria-label="Izoh" value={text}
      onChange={(e) => setText(e.target.value)} />
  );
}

function App() {
  const note = useRef(null);
  function print() {
    if (note.current.isEmpty()) {
      console.log("Chek: izohsiz");
      return;
    }
    console.log("Chek:", note.current.getText());
  }
  return (
    <div>
      <NoteBox ref={note} />
      <button onClick={print}>Chekka yozish</button>
    </div>
  );
}

Konsolda:

text
Chek: Qalampirsiz

Xato — bog'liqliklar []: handle birinchi renderdagi bo'sh text ni eslab qolgan edi. [text] bilan har o'zgarishda yangi handle yaratiladi. isEmpty ham text ni o'qiydi — u ham shu bog'liqlikka tayanadi.

3-mashq (qiyin): xatoli maydonga fokus

TypeScript'da OrderForm yozing: ism va son maydonlari, handle — OrderFormHandle:

  • reset() — ikkala maydonni tozalaydi (son — "1") va fokusni ism maydoniga qo'yadi;
  • focusFirstError() — birinchi xatoli maydonga fokus qo'yadi va uning yorlig'ini qaytaradi: ism bo'sh bo'lsa "Mehmon", son 1 dan kichik yoki son bo'lmasa "Soni", xato yo'q bo'lsa null.

Ota «Yuborish» tugmasida focusFirstError() ni chaqiradi va natijani konsolga yozadi. Ishora: ikkita ichki ref kerak, Number(qty) va Number.isInteger yordam beradi.

Yechim
tsx
import { useImperativeHandle, useRef, useState } from "react";
import type { Ref } from "react";

export type OrderFormHandle = {
  reset: () => void;
  focusFirstError: () => string | null;
};

function OrderForm({ ref }: { ref?: Ref<OrderFormHandle> }) {
  const nameInput = useRef<HTMLInputElement>(null);
  const qtyInput = useRef<HTMLInputElement>(null);
  const [name, setName] = useState("");
  const [qty, setQty] = useState("1");

  useImperativeHandle(ref, () => ({
    reset() {
      setName("");
      setQty("1");
      nameInput.current?.focus();
    },
    focusFirstError() {
      if (name.trim() === "") {
        nameInput.current?.focus();
        return "Mehmon";
      }
      const n = Number(qty);
      if (!Number.isInteger(n) || n < 1) {
        qtyInput.current?.focus();
        return "Soni";
      }
      return null;
    },
  }), [name, qty]);

  return (
    <div>
      <p>
        <label>
          Mehmon
          <input ref={nameInput} value={name}
            onChange={(e) => setName(e.target.value)} />
        </label>
      </p>
      <p>
        <label>
          Soni
          <input ref={qtyInput} value={qty}
            onChange={(e) => setQty(e.target.value)} />
        </label>
      </p>
    </div>
  );
}

export default function App() {
  const form = useRef<OrderFormHandle>(null);
  function submit() {
    const error = form.current?.focusFirstError() ?? null;
    console.log("Xato maydon:", error);
    const active = document.activeElement;
    console.log("Fokusda:", active?.closest("label")?.textContent);
  }
  return (
    <section>
      <OrderForm ref={form} />
      <button onClick={submit}>Yuborish</button>{" "}
      <button onClick={() => form.current?.reset()}>Tozalash</button>
    </section>
  );
}

Konsolda:

text
Xato maydon: Soni
Fokusda: Soni

Ekranda:

text
Mehmon
Soni
Yuborish Tozalash

focusFirstError state'ni o'qiydi — bog'liqliklar [name, qty]. Ota formaning tuzilishini bilmaydi: maydon qo'shilsa, faqat OrderForm o'zgaradi. Tozalash va xatoga fokus — ikkalasi ham "buyruq", holat emas: shuning uchun bu yerda handle o'rinli.

10. Real ishda

  • UI kutubxonalari (masalan, kod muharriri, xarita, video pleyer, matn muharriri) komponentlari ko'pincha ref orqali cheklangan API beradi: editor.focus(), map.flyTo(...), player.seekTo(...). Ularning hujjatidagi "ref methods" bo'limi — aynan useImperativeHandle.
  • Dizayn tizimlari (kompaniyaning umumiy komponentlari) Input, Select kabi komponentlarda focus() ni ochadi — formalar xatoli maydonga fokus qo'yishi uchun (Fokus boshqaruvi).
  • Kod ko'rigida "open() metodi o'rniga isOpen prop qiling" — tez-tez uchraydigan izoh.
  • Intervyu savollari: "useImperativeHandle nima uchun kerak?", "ref prop bilan farqi nima?", "React 19 da forwardRef nima bo'ldi?", "Qachon imperativ API — yomon tanlov?".

Xulosa

  • ref prop otaga butun DOM elementini beradi; useImperativeHandle esa faqat tanlangan metodlarni.
  • Handle commit'da qo'yiladi: renderda null, komponent olib tashlansa yana null.
  • Handle state o'qisa — state bog'liqliklarda bo'lsin, aks holda eskirgan qiymat qaytadi.
  • TypeScript'da ref?: Ref<Handle> — ota handle'da yo'q narsani so'rasa, TS2339.
  • React 19 da forwardRef kerak emas; eski kodda uni o'qiy olasiz.
  • Holat (ochiq, tanlangan) — props; buyruq (fokus, aylantirish, belgilash) — handle.

Keyingi dars: useLayoutEffect va vizual miltillashning oldini olish — DOM'ni o'lchab, ekran chizilishidan oldin joylashuvni tuzatishni o'rganamiz.

Manbalar

  • React: useImperativeHandle — react.dev/reference/react/useImperativeHandle
  • React: "Manipulating the DOM with Refs" — react.dev/learn/manipulating-the-dom-with-refs
  • React 19 e'loni: "ref as a prop" (2024-12-05) — react.dev/blog/2024/12/05/react-19
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React useImperativeHandle: komponentning imperativ API'si — ota faqat kerakli metodlarni oladi — IlmHamroh