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

React custom hooklar I: takrorlanadigan mantiqni use… funksiyasiga ajratib olish

Qisqacha: Custom hook — nomi use bilan boshlanadigan va ichida boshqa hooklarni chaqiradigan oddiy funksiya: function useOnline() { return useSyncExternalStore(…); }. U bir nechta komponentda takrorlanadigan mantiqni bir joyga yig'adi, lekin state'ni ulashmaydi: har komponent hook'ni chaqirganda o'z nusxasini oladi. Ikki-uch qiymat qaytarsa — useState kabi massiv (tuple), ko'p bo'lsa — obyekt. Hooklar qoidalari custom hook ichida ham, uni chaqirgan joyda ham amal qiladi.

Bu darsda

  • Custom hook nima ekanini va oddiy funksiyadan farqini tushuntirasiz.
  • "Mantiq ulashiladi, state — yo'q" qoidasini jonli misolda ko'rsatasiz.
  • Komponentdan hook'ni qadamma-qadam ajratib olasiz.
  • Tuple yoki obyekt qaytarishni, parametrlarni va hooklar kompozitsiyasini to'g'ri tanlaysiz.
  • vazifalar dagi App.tsx dan useOnlayn, useUrlTanlovi, useElon ni ajratasiz.

Oldin bilishingiz kerak: Hooklar qoidalari, useEffect III: cleanup, useRef I, useSyncExternalStore.

1. Nega bu kerak?

vazifalar dagi App.tsx holat 42 da 590 qatordan iborat. Unda effektlar, ref'lar, useSyncExternalStore lar, taymerlar — hammasi bitta komponentda aralashgan. "Internet bormi?" degan mantiq (obuna funksiyasi, navigator.onLine, server uchun taxmin) o'n beshta qator egallaydi. Ertaga u boshqa komponentga ham kerak bo'lsa — o'n besh qatorni nusxalaysizmi?

Funksiya e'lon qilish va chaqirish da takrorlanadigan kodni funksiyaga chiqarishni o'rgangan edik. Komponentlar ham funksiya, demak ularning ichidagi mantiqni ham funksiyaga chiqarish mumkin. Faqat bitta farq bor: bu mantiq ichida hooklar bor. Hook chaqiradigan funksiya — custom hook ("o'z hook'ingiz").

Hayotiy o'xshatish: «Bahor» oshxonasidagi retseptlar kitobi. Osh pishirish tartibi kitobda bir marta yozilgan. Ikki oshpaz bir vaqtda osh pishirsa, ular bitta retseptdan foydalanadi, lekin har biri o'z qozonida pishiradi. Biridagi osh ikkinchisinikiga aralashmaydi. Custom hook — retsept, state — qozon.

2. Birinchi custom hook

2.1 Takrorlangan mantiq

«Bahor» saytida ikki joyda internet holati kerak: tepadagi ogohlantirish va «Buyurtma berish» tugmasi (internet yo'q bo'lsa — o'chirilgan). useSyncExternalStore darsidagi kod ikki marta yozilgan:

jsx
function Banner() {
  const online = useSyncExternalStore(subscribe, getOnline);
  …
}

function OrderButton() {
  const online = useSyncExternalStore(subscribe, getOnline);
  …
}

Bitta qator — kichik takror. Lekin har joy subscribe va getOnline (() => navigator.onLine) ni import qilishi va serverda nima qaytarishni o'ylashi kerak. Hook buni bitta nomga yashiradi.

2.2 useOnline

jsx
import { useSyncExternalStore } from "react";

function subscribe(callback) {
  addEventListener("online", callback);
  addEventListener("offline", callback);
  return () => {
    removeEventListener("online", callback);
    removeEventListener("offline", callback);
  };
}

// Custom hook: "use" bilan boshlanadi, ichida hook chaqiradi
function useOnline() {
  return useSyncExternalStore(subscribe, () => navigator.onLine);
}

function Banner() {
  const online = useOnline();
  if (online) return null;
  return <p>Internet yo'q — buyurtma saqlanmaydi</p>;
}

function OrderButton() {
  const online = useOnline();
  return <button disabled={!online}>Buyurtma berish</button>;
}

function fakeOffline() { // faqat namuna uchun
  Object.defineProperty(navigator, "onLine", {
    value: false,
    configurable: true,
  });
  dispatchEvent(new Event("offline"));
}

function App() {
  return (
    <div>
      <Banner />
      <OrderButton />{" "}
      <button onClick={fakeOffline}>Internetni uzish</button>
    </div>
  );
}

Ekranda:

text
Internet yo'q — buyurtma saqlanmaydi
Buyurtma berish Internetni uzish

Banner va OrderButton endi "internet qanday aniqlanadi" ni bilmaydi — ular faqat useOnline() deydi. Ertaga aniqlash usuli o'zgarsa (masalan, serverga "ping" qo'shilsa), faqat hook o'zgaradi.

2.3 Custom hook — oddiy funksiya

React uchun custom hook hech qanday maxsus narsa emas. useOnline() ni chaqirish — ichidagi useSyncExternalStore ni chaqiruvchi komponentning o'zida chaqirish bilan bir xil. React hooklarni chaqiruv tartibidan taniydi (Hooklar qoidalari), funksiya nomlaridan emas. Shuning uchun:

  • hook ichidagi hooklar chaqiruvchi komponentga "tegishli" bo'ladi;
  • hooklar qoidalari ikkala joyda ham amal qiladi: custom hook'ni shart ichida chaqirib bo'lmaydi, uning ichida ham hooklar shartsiz chaqiriladi;
  • custom hook istalgan hook'ni chaqira oladi: useState, useEffect, useRef, use, boshqa custom hook.

Tekshirib ko'ring: function formatPrice(n) { return n + " so'm"; } — buni useFormatPrice deb atash kerakmi?

Javob

Yo'q. Ichida hech qanday hook yo'q — bu oddiy yordamchi funksiya. react.dev aniq aytadi: funksiya hooklarni chaqirmasa, unga use prefiksini bermang. Aks holda o'quvchi va linter uni hook deb hisoblab, keraksiz cheklovlar qo'yadi: masalan, uni shart ichida yoki sikl ichida chaqirib bo'lmaydi.

3. Mantiq ulashiladi, state — yo'q

Eng ko'p uchraydigan noto'g'ri tushuncha: "ikki komponent bitta custom hook'ni chaqirsa, ular umumiy state'ga ega bo'ladi". Tekshiramiz:

jsx
import { useState } from "react";

function useCounter(start) {
  const [count, setCount] = useState(start);
  const increment = () => setCount((c) => c + 1);
  return [count, increment];
}

function DishCounter({ name }) {
  const [count, increment] = useCounter(0);
  return (
    <p>
      {name}: {count} <button onClick={increment}>{name} +</button>
    </p>
  );
}

function App() {
  return (
    <div>
      <DishCounter name="Osh" />
      <DishCounter name="Manti" />
    </div>
  );
}

Ekranda:

text
Osh: 2 Osh +
Manti: 1 Manti +

Ikkala DishCounter bitta useCounter ni ishlatadi, lekin hisoblari alohida: osh — 2, manti — 1. Har chaqiruv o'z useState ini yaratadi — har oshpazning o'z qozoni.

Qadam Osh count Manti count
boshida 0 0
«Osh +» 1 0
«Osh +» 2 0
«Manti +» 2 1
flowchart TD
  H["useCounter — retsept (kod bitta)"] --> A["DishCounter Osh"]
  H --> B["DishCounter Manti"]
  A --> SA["o'z useState: 2"]
  B --> SB["o'z useState: 1"]

Retsept bitta, qozonlar ikkita: hook kodini ikkala komponent ishlatadi, lekin har biri o'z useState iga ega.

State'ni haqiqatan ulashish kerak bo'lsa — uni umumiy otaga ko'taring (State'ni ko'tarish), Context orqali bering (Context II) yoki tashqi store'da saqlang (useSyncExternalStore). Custom hook bu yo'llarni qulay qiladi (useCart() kabi), lekin o'zi state ulashmaydi.

4. Nomlash: use prefiksi

4.1 Nega aynan use

Nom — React uchun emas, odamlar va vositalar uchun. use bilan boshlangan nomni ko'rgan dasturchi biladi: "bu funksiya ichida state yoki effekt bo'lishi mumkin, uni shartsiz chaqirish kerak". Linter (eslint-plugin-react-hooks) va React Compiler ham hooklarni nomidan taniydi va qoidalarni faqat ularga qo'llaydi.

Sardor hook'ni onlaynmi deb atadi:

jsx
// "use" bilan boshlanmagan — oddiy funksiya
export function onlaynmi() {
  return useSyncExternalStore(subscribe, () => navigator.onLine);
}

ESLint (eslint-plugin-react-hooks 7.1.1) bilan tekshirdik:

text
  10:10  error  React Hook "useSyncExternalStore" is called in function "onlaynmi" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"  react-hooks/rules-of-hooks

"useSyncExternalStore hook'i onlaynmi funksiyasida chaqirilyapti, u esa na React komponenti, na custom hook. Komponent nomlari katta harf bilan, hook nomlari use so'zi bilan boshlanishi kerak." Kod ishlashi mumkin, lekin linter endi bu funksiyani himoya qila olmaydi. Masalan, kimdir uni if ichida chaqirsa, xato ko'rsatmaydi. Tuzatish — useOnlayn.

4.2 Nomlash qoidalari

Nima Nom Misol
komponent katta harf OrderButton
custom hook use + katta harf useOnline, useCart, useElon
hooksiz yordamchi oddiy fe'l yoki ot formatPrice, narxniHisobla

use dan keyin katta harf — useonline emas, useOnline. Kanonda o'zbekcha nomlar ham shu qoida bilan: useOnlayn, useElon, useUrlTanlovi.

5. Ajratib olish: qadamma-qadam

5.1 Boshlang'ich kod

«Bahor» buyurtma sahifasida e'lon chizig'i bor: "«Osh» savatga qo'shildi". U 3 soniyadan keyin o'zi yo'qolishi kerak, yangi e'lon esa eskisining taymerini bekor qiladi. vazifalar dagi App.tsx da ham aynan shunday kod bor. Avval hammasi komponent ichida (bu yerda tez sinash uchun muddat — 1 soniya):

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

const NOTICE_MS = 1000;

function App() {
  const [notice, setNotice] = useState("");
  const timer = useRef(undefined);

  useEffect(() => () => clearTimeout(timer.current), []);

  function announce(text) {
    clearTimeout(timer.current);
    setNotice(text);
    timer.current = setTimeout(() => setNotice(""), NOTICE_MS);
  }

  return (
    <div>
      <p role="status">{notice}</p>
      <button onClick={() => announce("«Osh» qo'shildi")}>
        Osh
      </button>{" "}
      <button onClick={() => announce("«Manti» qo'shildi")}>
        Manti
      </button>
    </div>
  );
}

Ekranda:

text
«Manti» qo'shildi
Osh Manti

Vaqt bo'yicha: «Osh» bosildi, 600 ms o'tdi, «Manti» bosildi — oshning taymeri bekor bo'ldi va mantining 1 soniyasi boshlandi. Yana 600 ms o'tgach, e'lon hali turibdi. Bitta funksiya uchun to'rtta narsa kerak: state, ref, cleanup effekti va ishlovchi.

5.2 To'rt qadam

  1. Takrorlanadigan qismni toping. Bu yerda: notice state'i, timer ref'i, cleanup effekti, announce funksiyasi. Ular faqat bir-biri bilan bog'liq, komponentning qolgan qismi bilan emas.
  2. Funksiyaga ko'chiring. Nomi use bilan: useNotice.
  3. Kirish va chiqishni belgilang. Kirish (parametr) — muddat, chiqish — joriy matn va e'lon qilish funksiyasi.
  4. Komponentda almashtiring va eski kodni o'chiring.
jsx
import { useEffect, useRef, useState } from "react";

function useNotice(ms) {
  const [notice, setNotice] = useState("");
  const timer = useRef(undefined);

  // Komponent yopilsa — kutayotgan taymer ham to'xtasin
  useEffect(() => () => clearTimeout(timer.current), []);

  function announce(text) {
    clearTimeout(timer.current);
    setNotice(text);
    timer.current = setTimeout(() => setNotice(""), ms);
  }

  return [notice, announce];
}

function App() {
  const [notice, announce] = useNotice(1000);
  return (
    <div>
      <p role="status">{notice}</p>
      <button onClick={() => announce("«Osh» qo'shildi")}>
        Osh
      </button>{" "}
      <button onClick={() => announce("«Manti» qo'shildi")}>
        Manti
      </button>
    </div>
  );
}

Ekranda:

text
«Manti» qo'shildi
Osh Manti

Xulq aynan o'sha, App esa bitta qatorga qisqardi. Hook — "e'lon" degan bitta tushuncha, uning ichidagi taymer va cleanup — tafsilot.

6. Nima qaytarish: tuple yoki obyekt

6.1 Tuple — useState kabi

useNotice massiv qaytaradi: [notice, announce]. Bunday aniq uzunlikdagi massiv tuple ("kortej") deb ataladi. Afzalligi — chaqiruvchi nomlarni o'zi tanlaydi: const [elon, elonQil] = useNotice(3000). Bitta komponentda hook'ni ikki marta chaqirsangiz, nomlar to'qnashmaydi.

TypeScript'da tuple uchun bitta nozik joy bor. Massiv qaytarsangiz, TypeScript uni "aralash turdagi massiv" deb oladi:

tsx
import { useState } from "react";

function useToggle(initial: boolean) {
  const [on, setOn] = useState(initial);
  const toggle = () => setOn(!on);
  return [on, toggle];
}

export default function App() {
  const [open, toggle] = useToggle(false);
  const label = open ? "Yopish" : "Ochish";
  return <button onClick={toggle}>{label}</button>;
}

Xato:

text
TS2322: Type 'boolean | (() => void)' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined'.

"boolean | (() => void) turi onClick ga mos emas." TypeScript [on, toggle] ni (boolean | (() => void))[] deb oldi: har element "yo boolean, yo funksiya". Tuzatish — qaytariladigan turni aniq yozish yoki as const:

tsx
import { useState } from "react";

function useToggle(initial: boolean): [boolean, () => void] {
  const [on, setOn] = useState(initial);
  const toggle = () => setOn(!on);
  return [on, toggle];
}

export default function App() {
  const [open, toggle] = useToggle(false);
  const label = open ? "Yopish" : "Ochish";
  return <button onClick={toggle}>{label}</button>;
}

Ekranda:

text
Yopish

Kanondagi useElon ham shunday: (): [string, (matn: string) => void].

6.2 Obyekt — ko'p qiymatda

Qiymatlar uch-to'rttadan oshsa, tuple chalkashadi: const [a, b, c, d] = useX() — qaysi biri nima? Bunda obyekt qaytaring: const { filtr, saralash } = useUrlTanlovi(). Nomlar qat'iy, tartib ahamiyatsiz, keraklisini olasiz.

Shakl Qachon Misol
bitta qiymat hook bitta narsa beradi const online = useOnline()
tuple 2 ta, useState ga o'xshash juftlik const [elon, elonQil] = useElon()
obyekt 3+ ta yoki nomlari qat'iy bo'lishi kerak const { filtr, saralash } = useUrlTanlovi()

7. Parametrlar va kompozitsiya

7.1 Parametr — har renderda yangi

Hook parametri — oddiy funksiya argumenti: har renderda yangi qiymat keladi. Hook ichida effektda ishlatilsa, u bog'liqliklarga kiradi (useEffect II):

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

function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
    console.log("sarlavha:", title);
  }, [title]);
}

function App() {
  const [count, setCount] = useState(0);
  useDocumentTitle(`Savat (${count}) — Bahor`);
  return <button onClick={() => setCount(count + 1)}>Osh +</button>;
}

Konsolda:

text
sarlavha: Savat (0) — Bahor
sarlavha: Savat (1) — Bahor
sarlavha: Savat (2) — Bahor

Ekranda:

text
Osh +

Brauzer yorlig'idagi sarlavha savatga ergashadi. Hook parametrni "eslab qolmaydi" — har renderda komponent unga joriy qiymatni beradi.

7.2 Hooklar hooklarni chaqiradi

Custom hook boshqa custom hook'ni chaqira oladi — kichik bo'laklardan kattasini yig'ish (kompozitsiya). «Buyurtma berish mumkinmi?» — internet bor va savat bo'sh emas:

jsx
import {
  createContext,
  use,
  useState,
  useSyncExternalStore,
} from "react";

const CartContext = createContext(null);

function subscribe(callback) {
  addEventListener("online", callback);
  addEventListener("offline", callback);
  return () => {
    removeEventListener("online", callback);
    removeEventListener("offline", callback);
  };
}
function useOnline() {
  return useSyncExternalStore(subscribe, () => navigator.onLine);
}

function useCart() {
  const cart = use(CartContext);
  if (cart === null) throw new Error("useCart() — Provider yo'q");
  return cart;
}

// Kompozitsiya: ikki hook — bitta javob
function useCanOrder() {
  const online = useOnline();
  const { items } = useCart();
  return online && items.length > 0;
}

function OrderButton() {
  const canOrder = useCanOrder();
  return <button disabled={!canOrder}>Buyurtma berish</button>;
}

function App() {
  const [items, setItems] = useState([]);
  return (
    <CartContext value={{ items }}>
      <button onClick={() => setItems([...items, "Osh"])}>
        Osh qo'shish
      </button>{" "}
      <OrderButton />
      <p>Savatda: {items.length}</p>
    </CartContext>
  );
}

setTimeout(() => {
  const order = document.querySelectorAll("button")[1];
  console.log("o'chirilganmi:", order.disabled);
}, 100);

Konsolda:

text
o'chirilganmi: false

Ekranda:

text
Osh qo'shish Buyurtma berish
Savatda: 1

OrderButton uchta manbani (internet, kontekst, qoida) bilmaydi — faqat useCanOrder(). Qoida o'zgarsa ("ish vaqti ham bo'lsin"), bitta hook o'zgaradi.

8. Hook'ni test qilish

Custom hook — funksiya, lekin uni oddiy chaqirib bo'lmaydi: hooklar faqat render paytida ishlaydi. Testing Library'da buning uchun renderHook bor — u hook'ni kichik sinov komponenti ichida chaqiradi. vazifalar dagi test (Vitest, soxta taymerlar bilan):

tsx
describe("useElon", () => {
  test("e'lon 3 s turadi, yangisi taymerni boshidan boshlaydi", () => {
    vi.useFakeTimers();
    const { result } = renderHook(() => useElon());
    act(() => result.current[1]("Birinchi"));
    act(() => vi.advanceTimersByTime(2000));
    act(() => result.current[1]("Ikkinchi"));
    act(() => vi.advanceTimersByTime(2000));
    expect(result.current[0]).toBe("Ikkinchi");
    act(() => vi.advanceTimersByTime(1000));
    expect(result.current[0]).toBe("");
  });
});

result.current — hook'ning eng so'nggi qaytargani: [0] — matn, [1] — elonQil. vi.useFakeTimers() va advanceTimersByTime vaqtni "qo'lda" suradi — test 5 soniya kutmaydi. Komponent testlarini testlar qismida chuqur o'rganamiz.

9. Vazifalar qadami: App.tsx dan hooklar (1-qism)

9.1 Nima chiqariladi

Yangi branch: refactor/hooklar. App.tsx dan uchta tushuncha src/hooklar/ papkasiga chiqadi:

Hook Nima edi App.tsx da Qaytaradi
useOnlayn onlaynObuna, onlaynmi, serverdaOnlayn + useSyncExternalStore (#38) boolean
useUrlTanlovi ikki useSyncExternalStore(urlgaObuna, …) (#38) { filtr, saralash }
useElon useState(""), elonTaymeri ref (#34), elonQil funksiyasi [elon, elonQil]

9.2 useOnlayn.ts

ts
// useOnlayn.ts — internet bormi (17/#43). App ichidagi
// useSyncExternalStore (#38) alohida funksiyaga chiqdi: endi har
// qanday komponent bir qator bilan oladi. Hook — "use" bilan
// boshlanadigan oddiy funksiya, ichida boshqa hooklar; STATE emas,
// MANTIQ ulashiladi
import { useSyncExternalStore } from "react";

function obuna(tinglovchi: () => void): () => void {
  addEventListener("online", tinglovchi);
  addEventListener("offline", tinglovchi);
  return () => {
    removeEventListener("online", tinglovchi);
    removeEventListener("offline", tinglovchi);
  };
}

export function useOnlayn(): boolean {
  return useSyncExternalStore(
    obuna,
    () => navigator.onLine,
    () => true, // serverda (SSR) — taxmin; bizda SSR yo'q
  );
}

Izoh qatorlari telefonga sig'ishi uchun qayta bo'lindi (bu va keyingi fayllarda), matni o'sha. obuna — modul darajasida, barqaror (useSyncExternalStore dagi ikkinchi qoida hook ichida ham saqlandi).

9.3 useUrlTanlovi.ts va useElon.ts

ts
// useUrlTanlovi.ts — filtr va saralash URL'dan (17/#43; #38
// dagi useSyncExternalStore). URL'ga yozish — marshrut.ts
// (filtrniYoz...)
export function useUrlTanlovi(): { filtr: Filtr; saralash: Saralash } {
  const filtr = useSyncExternalStore(urlgaObuna, () => filtrniOl());
  const saralash = useSyncExternalStore(urlgaObuna, () =>
    saralashniOl(),
  );
  return { filtr, saralash };
}

Ikki qiymat, lekin obyekt: nomlar qat'iy bo'lsin, filtr va saralash almashib ketmasin. useElon — darsdagi useNotice ning kanon varianti:

ts
const ELON_MUDDATI = 3000;

/**
 * @returns [joriy e'lon, e'lon qilish funksiyasi] — useState kabi
 * juftlik
 */
export function useElon(): [string, (matn: string) => void] {
  const [elon, setElon] = useState("");
  const taymer = useRef<ReturnType<typeof setTimeout>>(undefined);

  // Komponent yopilsa — kutayotgan taymer ham to'xtasin
  useEffect(() => () => clearTimeout(taymer.current), []);

  function elonQil(matn: string) {
    clearTimeout(taymer.current);
    setElon(matn);
    if (matn !== "") {
      taymer.current = setTimeout(() => setElon(""), ELON_MUDDATI);
    }
  }

  return [elon, elonQil];
}

Farqi bitta: bo'sh matn (elonQil("")) taymer qo'ymaydi — kanonda har yangi amal oldidan e'lon shu bilan tozalanadi. Importlar va fayl boshidagi izohlar tushirib qoldirildi.

9.4 App.tsx da

diff
-  const filtr = useSyncExternalStore(urlgaObuna, () => filtrniOl());
-  const saralash = useSyncExternalStore(urlgaObuna, () =>
-    saralashniOl(),
-  );
+  // Filtr va saralash — URL'dan (useSyncExternalStore, 17/#38):
+  // state'da nusxasi yo'q. 17/#43 dan — custom hook ichida
+  const { filtr, saralash } = useUrlTanlovi();
 …
-  const onlayn = useSyncExternalStore(
-    onlaynObuna,
-    onlaynmi,
-    serverdaOnlayn,
-  );
-  const aloqa: TarmoqHolati = onlayn ? tarmoq : "oflayn";
-  const [elon, setElon] = useState("");
+  const aloqa: TarmoqHolati = useOnlayn() ? tarmoq : "oflayn";
+  // E'lon: matn (state) + 3 s taymeri (ref) — custom hook (17/#43)
+  const [elon, elonQil] = useElon();

Modul darajasidagi onlaynObuna, onlaynmi, serverdaOnlayn, ELON_MUDDATI, komponentdagi elonTaymeri ref'i va elonQil funksiyasi — o'chdi. elonQil ni chaqiradigan o'nlab joy o'zgarmadi: hook o'sha nomli funksiyani qaytaradi. Bu — yaxshi ajratishning belgisi.

Commit hali yo'q: keyingi darsda yana ikki hook — oyna hodisalari va media so'rovi — qo'shiladi, testlar bilan birga bitta commit bo'ladi.

10. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
React Hook "…" is called in function "…" that is neither… hook chaqiradigan funksiya nomi use bilan boshlanmagan useX deb nomlang
ikki komponent "bir state'ni" o'zgartirmaydi custom hook state ulashmaydi state'ni ko'taring yoki Context
TS2322: Type 'boolean | (() => void)' … tuple turi kengaydi qaytish turini yozing yoki as const
hook eski parametr bilan ishlaydi effektda parametr bog'liqliklarda yo'q [param] qo'shing
hooksiz funksiya useFormat… deb atalgan keraksiz hook cheklovlari oddiy nom bering
custom hook if ichida chaqirilgan hooklar qoidasi buzilgan shartsiz chaqiring, shartni ichkarida tekshiring

11. Mashqlar

Mashqlar kurs/mashqlar/17/43-custom-hooklar/ da.

1-mashq (oson): useToggle

Bo'sh joylarni to'ldiring:

jsx
function [___:useToggle](initial) {
  const [on, setOn] = [___:useState](initial);
  const toggle = () => setOn((v) => !v);
  return [on, [___:toggle]];
}
Yechim
jsx
import { useState } from "react";

function useToggle(initial) {
  const [on, setOn] = useState(initial);
  const toggle = () => setOn((v) => !v);
  return [on, toggle];
}

function App() {
  const [menuOpen, toggleMenu] = useToggle(false);
  const [notesOpen, toggleNotes] = useToggle(false);
  return (
    <div>
      <button onClick={toggleMenu}>Menyu</button>{" "}
      <button onClick={toggleNotes}>Izohlar</button>
      <p>Menyu: {menuOpen ? "ochiq" : "yopiq"}</p>
      <p>Izohlar: {notesOpen ? "ochiq" : "yopiq"}</p>
    </div>
  );
}

Ekranda:

text
Menyu Izohlar
Menyu: yopiq
Izohlar: ochiq

Bitta komponentda hook ikki marta — tuple'ning foydasi: nomlarni o'zimiz tanladik, ikki holat mustaqil.

2-mashq (o'rta): chegarali hisoblagich

TypeScript'da useCounter({ start, min, max }) yozing. U obyekt qaytarsin: count, increment, decrement, reset, atMin, atMax. Hisob min dan kichik va max dan katta bo'lmasin. «Bahor» bron formasida ikki hisoblagich: mehmonlar (1–12, boshi 2) va bolalar uchun stul (0–3, boshi 0). Chegarada tugma disabled bo'lsin.

Yechim
tsx
import { useState } from "react";

type CounterOptions = { start: number; min: number; max: number };

export function useCounter({ start, min, max }: CounterOptions) {
  const [count, setCount] = useState(start);
  const clamp = (n: number) => Math.min(max, Math.max(min, n));
  return {
    count,
    increment: () => setCount((c) => clamp(c + 1)),
    decrement: () => setCount((c) => clamp(c - 1)),
    reset: () => setCount(start),
    atMin: count <= min,
    atMax: count >= max,
  };
}

function Stepper({ label, options }: {
  label: string;
  options: CounterOptions;
}) {
  const c = useCounter(options);
  return (
    <p>
      {label}: {c.count}{" "}
      <button onClick={c.decrement} disabled={c.atMin}>
        {label} −
      </button>{" "}
      <button onClick={c.increment} disabled={c.atMax}>
        {label} +
      </button>
    </p>
  );
}

const GUESTS = { start: 2, min: 1, max: 12 };
const CHAIRS = { start: 0, min: 0, max: 3 };

export default function App() {
  return (
    <div>
      <Stepper label="Mehmon" options={GUESTS} />
      <Stepper label="Stul" options={CHAIRS} />
    </div>
  );
}

Ekranda:

text
Mehmon: 1 Mehmon − Mehmon +
Stul: 1 Stul − Stul +

Ikkinchi «Mehmon −» 1 dan pastga tushirmadi (clamp), tugma esa disabled bo'ldi. Olti qiymat — obyekt: c.atMin ni c.count bilan adashtirib bo'lmaydi. Ikki Stepper — ikki mustaqil state.

3-mashq (qiyin): orqaga sanoq

useCountdown(seconds) yozing: { left, running, start, stop } qaytarsin. start() — sanoqni boshlaydi (har soniyada left −1), 0 ga yetsa o'zi to'xtaydi; stop() — to'xtatadi; komponent yopilsa — interval tozalanadi. «Bahor» oshxona ekranida: "Osh tayyor bo'lishiga: 3 s". Sinash uchun "soniya" o'rniga tick parametri bering: useCountdown(3, 100) — har 100 ms da bittadan.

Ishora: interval running ga bog'liq effektda (useEffect III): running true bo'lsa — setInterval, cleanup — clearInterval. 0 ga yetganini updater funksiyada tekshiring (State II).

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

function useCountdown(seconds, tick = 1000) {
  const [left, setLeft] = useState(seconds);
  const [running, setRunning] = useState(false);

  useEffect(() => {
    if (!running) return undefined;
    const id = setInterval(() => {
      setLeft((s) => {
        if (s <= 1) {
          setRunning(false);
          return 0;
        }
        return s - 1;
      });
    }, tick);
    return () => clearInterval(id);
  }, [running, tick]);

  return {
    left,
    running,
    start: () => {
      if (left > 0) setRunning(true);
    },
    stop: () => setRunning(false),
  };
}

function App() {
  const { left, running, start, stop } = useCountdown(3, 100);
  const done = left === 0 ? "Tayyor!" : "—";
  return (
    <div>
      <p>Osh tayyor bo'lishiga: {left} s</p>
      <p>{running ? "Sanoq ketyapti" : done}</p>
      <button onClick={start}>Boshlash</button>{" "}
      <button onClick={stop}>To'xtatish</button>
    </div>
  );
}

Ekranda:

text
Osh tayyor bo'lishiga: 0 s
Tayyor!
Boshlash To'xtatish

500 ms kutildi — 3 ta "soniya" (har biri 100 ms) o'tib, sanoq o'zi to'xtadi. Interval faqat running paytida yashaydi: to'xtaganda yoki komponent yopilganda cleanup uni o'chiradi. setRunning(false) updater ichida chaqirilgani — ataylab: shu yerda 0 ga yetganimizni aniq bilamiz. Haqiqiy loyihada tick — 1000.

12. Real ishda

  • Har loyihada src/hooks/ (bizda src/hooklar/) papkasi bor: useAuth, useDebounce, useMediaQuery, useLocalStorage. Komponentlar qisqa, mantiq — hooklarda.
  • Kutubxonalar o'z API'sini hooklar orqali beradi: router (useNavigate), forma (useForm), server holati (useQuery). Ularni o'qish uchun custom hook tushunchasi shart.
  • Kod ko'rigida "bu effekt va state boshqa komponentda ham bor — hook'ga chiqaring" — eng ko'p uchraydigan taklif; teskarisi ham: "bu hook faqat bir joyda va ikki qator — ajratish shart emas".
  • Intervyu savollari: "Custom hook nima va u state'ni ulashadimi?", "Nega nomi use bilan boshlanadi?", "Tuple yoki obyekt qaytarish qachon?", "Custom hook'ni qanday test qilasiz?".

Xulosa

  • Custom hook — use bilan boshlanadigan, ichida hook chaqiradigan oddiy funksiya.
  • Mantiq ulashiladi, state — yo'q: har chaqiruv o'z nusxasini oladi.
  • use prefiksi linter va odamlar uchun; hooksiz funksiyaga bermang.
  • Ajratish: takrorni toping → funksiyaga ko'chiring → kirish/chiqishni belgilang → almashtiring.
  • 1–2 qiymat — tuple (TS'da qaytish turini yozing), ko'p — obyekt; parametr effektda ishlatilsa — bog'liqlikda.
  • vazifalar: useOnlayn, useUrlTanlovi, useElon — App.tsx dan src/hooklar/ ga.

Keyingi dars: Custom hooklar II: tayyor retseptlar va hook kutubxonalari — useLocalStorage, useDebounce, useMediaQuery kabi retseptlarni yozamiz va tayyor kutubxonalarni taqqoslaymiz.

Manbalar

  • React: "Reusing Logic with Custom Hooks" — react.dev/learn/reusing-logic-with-custom-hooks
  • React: "Rules of Hooks" — react.dev/reference/rules/rules-of-hooks
  • Testing Library: renderHook — testing-library.com/docs/react-testing-library/api
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React custom hooklar I: takrorlanadigan mantiqni use… funksiyasiga ajratib olish — IlmHamroh