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

Hooklar qoidalari: nega hook if ichida chaqirilmaydi va React ularni qanday eslaydi

Qisqacha: React har komponentning hooklarini nomi bo'yicha emas, chaqirilish tartibi bo'yicha eslaydi: birinchi useState — 1-katak, ikkinchisi — 2-katak. Shuning uchun ikki qoida bor: hook faqat komponent (yoki use bilan boshlanadigan custom hook) ichida va faqat yuqori darajada chaqiriladi — if, sikl, erta return dan keyin, ichki funksiya yoki hodisa ishlovchisida emas. Qoida buzilsa, React Rendered more hooks than during the previous render deydi yoki, eng yomoni, qiymatlarni jim almashtirib yuboradi. ESLint'ning react-hooks/rules-of-hooks qoidasi buni kod yozayotganda ushlaydi; TypeScript esa ko'rmaydi. Yagona istisno — React 19 dagi use(): uni shart ichida chaqirsa bo'ladi.

Bu darsda

  • React hooklarni qanday eslashini o'z qo'lingiz bilan yozilgan 20 qatorlik model orqali tushuntira olasiz.
  • Hooklarning ikki qoidasini va ularning har bir buzilish shaklini taniysiz.
  • Rendered more hooks…, Rendered fewer hooks… va "tartib o'zgardi" xabarlarini o'qib, sababini topasiz.
  • ESLint rules-of-hooks xabarlarini tushunasiz va shartli hookni to'g'ri shaklga keltirasiz.
  • use() nega istisno ekanini bilasiz.

Oldin bilishingiz kerak: State I: useState — komponent xotirasi, Shartli render, ESLint II: React va TypeScript loyiha uchun plaginlar.

1. Nega bu kerak?

Mini-ilova darsida vazifalar ning butun interfeysi React'ga o'tdi. Endi oldinda yangi hooklar bor: useEffect, useRef, useEffectEvent. Ularning hammasi bitta umumiy qoidaga bo'ysunadi. Uni bugun bir marta yaxshilab tushunib olsak, keyingi o'nta darsda qoqilmaymiz.

Sardor tahrir qatori uchun kichik komponent yozdi. Mantiq oddiy edi: "tahrir rejimida bo'lmasa — hech narsa chizma; bo'lsa — maydon va uning state'i":

tsx
export function Tajriba({ tahrirda }: { tahrirda: boolean }) {
  if (!tahrirda) {
    return null;
  }
  const [matn, setMatn] = useState("");
  return <input value={matn} onChange={(e) => setMatn(e.target.value)} />;
}

Kod o'qilishi chiroyli. TypeScript ham jim: npx tsc -b — exit 0. Lekin vazifalar dagi ESLint darhol qizil chiziq chizdi (haqiqiy chiqish):

text
  7:27  error  React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?  react-hooks/rules-of-hooks

"useState hooki shart ostida chaqirilgan. React hooklari har chizishda aynan bir xil tartibda chaqirilishi shart. Tasodifan erta return dan keyin chaqirmadingizmi?" Sardor hayron: "Funksiya-ku, if ichida nima chaqirsam ham bo'lardi-ku?"

Oddiy funksiyada — ha. Hook esa boshqacha. Hayotiy o'xshatish: kiyim saqlash xonasi (garderob). Siz paltongizni topshirasiz, sizga raqam beriladi — 1, 2, 3. Garderobchi ismingizni so'ramaydi, faqat raqamga qaraydi. Agar kimdir navbatsiz kirib, raqamlar siljib ketsa — siz birovning paltosini olib ketasiz. React hooklari aynan shunday raqamlangan.

2. React hooklarni qanday eslaydi

2.1 Komponent — har safar qaytadan chaqiriladigan funksiya

useState darsida ko'rganimizdek, komponent har chizishda boshidan oxirigacha qayta chaqiriladi. Uning ichidagi let va const lar har safar yangidan yaratiladi. Unda state qayerda yashaydi? Komponent funksiyasining ichida emas — React'ning o'zida, shu komponentga bog'langan ro'yxatda.

useState("Osh") chaqirilganda funksiya o'zidan "qaysi state?" deb so'ramaydi. Unga nom berilmaydi — dish degan nom faqat sizning kodingizda. React faqat bitta narsani biladi: "bu chizishdagi nechanchi hook chaqiruvi".

2.2 O'zimiz yasab ko'ramiz

Buni tushunishning eng yaxshi yo'li — React'ning soddalashtirilgan modelini o'zimiz yozish. Haqiqiy React ancha murakkab, lekin g'oya aynan shu: ro'yxat va hisoblagich.

js
// React xotirasining soddalashtirilgan modeli
const slots = []; // komponent xotirasi: katakchalar ro'yxati
let index = 0; // hozir nechanchi hook chaqirilyapti

function useState(initial) {
  const i = index; // shu chaqiruvning raqami
  index++;
  if (slots.length <= i) slots.push(initial); // birinchi marta
  const setState = (value) => {
    slots[i] = value;
  };
  return [slots[i], setState];
}

function render(component, props) {
  index = 0; // har chizish boshida hisob noldan
  return component(props);
}

function OrderCard({ showNote }) {
  const [dish] = useState("Osh");
  let note = "";
  if (showNote) {
    [note] = useState("piyozsiz"); // ❌ shart ichida
  }
  const [count] = useState(1);
  return `${dish} × ${count}${note ? ", " + note : ""}`;
}

console.log(render(OrderCard, { showNote: false })); // Osh × 1
console.log(render(OrderCard, { showNote: true })); // Osh × 1, 1
console.log(slots); // [ 'Osh', 1, 1 ]

Ikkinchi chizishda izoh "piyozsiz" bo'lishi kerak edi, lekin ekranda 1 — sonning qiymati! Nima bo'ldi? Qadamma-qadam ko'ramiz:

Chaqiruv 1-chizish (showNote: false) 2-chizish (showNote: true)
1-hook dish → katak 0 = "Osh" dish → katak 0 = "Osh"
2-hook count → katak 1 = 1 note → katak 1 = 1
3-hook — count → katak 2 yangi = 1

Birinchi chizishda katak 1 ni count egallagan. Ikkinchi chizishda shart ochildi va katak 1 ni note "o'qib oldi". count esa yangi, bo'sh katakka tushdi. Hech qanday xato chiqmadi — model shunchaki noto'g'ri qiymat berdi. Garderobda raqamlar siljidi.

flowchart LR
  subgraph R1["1-chizish"]
    A1["useState: dish"] --> K0["katak 0: Osh"]
    B1["useState: count"] --> K1["katak 1: 1"]
  end
  subgraph R2["2-chizish, shart ochiq"]
    A2["useState: dish"] --> K0b["katak 0: Osh"]
    B2["useState: note"] --> K1b["katak 1: 1 ❌"]
    C2["useState: count"] --> K2b["katak 2: yangi"]
  end

Diagrammada chap ustun — birinchi chizish, o'ng ustun — ikkinchi. O'ng tomonda note ga qarang: u boshqa hookning katagiga tushib qolgan.

Tekshirib ko'ring: Modelda if (showNote) blokini const [count] = useState(1) qatoridan keyinga ko'chirsak, ikkinchi chizishda count to'g'ri bo'ladimi? note chi?

Javob

count to'g'ri bo'ladi: u ikkala chizishda ham 2-chaqiruv, katak 1. note esa 3-chaqiruv bo'lib, yangi katak 2 ni oladi — "piyozsiz". Bu safar tasodifan to'g'ri ishladi. Lekin showNote yana false bo'lsa, katak 2 eslab qolinadi, keyin yana ochilganda eski qiymat qaytadi — xatti-harakat shartga bog'liq bo'lib qoladi. Qoida "ba'zan ishlaydi" uchun emas: hook har doim bir xil tartibda bo'lsa, umuman o'ylash shart emas.

2.3 Haqiqiy React: uch xil natija

Haqiqiy React modelimizdan aqlliroq: u har katakka hook turini ham yozib qo'yadi. Shuning uchun qoida buzilganda uch xil natija bo'ladi. Uchalasini jonli ko'ramiz.

Hook qo'shildi. Tugma bosilganda shart ochiladi va yangi useState paydo bo'ladi:

jsx
import { useState } from "react";

function App() {
  const [editing, setEditing] = useState(false);
  if (editing) {
    const [text, setText] = useState("Osh");
  }
  return <button onClick={() => setEditing(true)}>Tahrirlash</button>;
}

Konsolda:

text
React has detected a change in the order of Hooks called by App. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks
   Previous render            Next render
   ------------------------------------------------------
1. useState                   useState
2. undefined                  useState
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Uncaught Error: Rendered more hooks than during the previous render.

Ikki xabar keldi. Birinchisi — ogohlantirish: "React App chaqirgan hooklar tartibi o'zgarganini aniqladi. Tuzatilmasa, bu xatolarga olib keladi." Ostidagi jadval juda foydali: chap ustun — oldingi chizish, o'ng — keyingisi. 2-qatorda oldin hech narsa (undefined) yo'q edi, endi useState bor. Ikkinchisi — xato: "Oldingi chizishdagidan ko'proq hook chizildi." Saytda «Komponentni chizishda xato» qutisi chiqadi, Maslahat qatorida esa — hooklar tartibi haqida eslatma.

Hook kamaydi. Erta return — Sardorning holati. Avval ikki hook chaqiriladi, tugmadan keyin esa bittasi:

jsx
import { useState } from "react";

function App() {
  const [open, setOpen] = useState(true);
  if (!open) return <p>Yopiq</p>;
  const [price, setPrice] = useState(35000);
  return <button onClick={() => setOpen(false)}>Yopish</button>;
}

Konsolda:

text
Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

"Kutilganidan kamroq hook chizildi. Bunga tasodifiy erta return sabab bo'lishi mumkin." React hatto sababni taxmin qilib aytyapti.

Tartib o'zgardi, soni o'sha. Bu eng xavflisi. Ikki useState o'rin almashsa, React turlarni solishtiradi — ikkalasi ham useState. Hech qanday xabar yo'q:

jsx
import { useState } from "react";

function App() {
  const [swapped, setSwapped] = useState(false);
  let dish, price;
  if (swapped) {
    [price] = useState(35000);
    [dish] = useState("Osh");
  } else {
    [dish] = useState("Osh");
    [price] = useState(35000);
  }
  return (
    <div>
      <p>Taom: {dish}</p>
      <p>Narx: {price}</p>
      <button onClick={() => setSwapped(true)}>Almashtir</button>
    </div>
  );
}

Ekranda:

text
Taom: 35000
Narx: Osh
Almashtir

Konsol toza, ilova yiqilmadi — lekin taom nomi o'rnida narx turibdi. Bizning 20 qatorlik modelimiz bilan aynan bir xil xatti-harakat. Real ilovada bu "ba'zan noto'g'ri mijozga noto'g'ri narx" bo'lib chiqadi. Bunday xatoni topish soatlab vaqt oladi.

Diqqat: Uchinchi holatda React jim qoldi. Shuning uchun hooklar qoidasiga ish vaqtidagi xatoga tayanib emas, linter bilan rioya qilinadi — u kodni ishga tushirmasdan, yozayotgan paytingizda ushlaydi.

3. Ikki qoida

React hujjati ularni "Rules of Hooks" (hooklar qoidalari) deb ataydi. Ikkalasi ham «React hooklarni qanday eslaydi» bo'limidagi modeldan kelib chiqadi.

3.1 Birinchi qoida: faqat yuqori darajada

Hook komponent funksiyasining yuqori darajasida chaqiriladi — har qanday return dan oldin, hech qanday blok ichida emas. Har bir chizishda hamma hooklar bir xil sonda, bir xil tartibda chaqirilsin.

Joy Mumkinmi Nega
funksiya boshida, return dan oldin tartib doim bir xil
if / ternary / && ichida soni o'zgaradi
for / .map() ichida soni elementlar soniga bog'liq
erta return dan keyin ba'zan chaqirilmaydi
hodisa ishlovchisi ichida chizish paytida emas, bosilganda
useEffect ga berilgan funksiya ichida chizish tugagach chaqiriladi
try / catch ichida xato bo'lsa — keyingilari chaqirilmaydi

Oxirgi uch qator haqida: hook — "chizish paytidagi" chaqiruv. React hisoblagichni faqat komponent chizilayotgan paytda yuritadi. Tugma bosilganda yoki effekt ishlaganda chizish allaqachon tugagan — hookning raqami yo'q.

3.2 Ikkinchi qoida: faqat React funksiyalaridan

Hook faqat ikki joydan chaqiriladi:

  1. Komponent — katta harf bilan boshlanadigan funksiya (Komponentlar darsidagi qoida).
  2. Custom hook — use bilan boshlanadigan o'zingizning funksiyangiz (useCart, useOnline).

Oddiy yordamchi funksiya (formatPrice, calculateTotal) ichida hook chaqirib bo'lmaydi. Sabab — o'sha model: React hisoblagichi faqat komponent chizilayotgan paytda bor. Oddiy funksiyani esa istalgan joydan, istalgan payt chaqirish mumkin.

Custom hook (custom hook) — ichida boshqa hooklar chaqiradigan, nomi use bilan boshlanadigan funksiya. Nomdagi use — kelishuv emas, signal: linter faqat shu nom bo'yicha "bu hook" deb taniydi va unga ham qoidalarni qo'llaydi. O'z hooklarimizni Custom hooklar I darsida yozamiz. Hozircha shuni eslang: use bilan boshlanadigan nom faqat haqiqiy hooklarga beriladi.

Tekshirib ko'ring: Quyidagilardan qaysi biri qoidani buzadi? (a) function Cart() { const [n] = useState(0); … }; (b) function getCart() { const [n] = useState(0); … }; (c) function useCart() { const [n] = useState(0); … }.

Javob

Faqat (b). Cart — katta harfli komponent, useCart — custom hook, ikkalasida ham hook mumkin. getCart — oddiy funksiya: ESLint React Hook "useState" is called in function "getCart" that is neither a React function component nor a custom React Hook function deydi.

4. ESLint qoidani qanday ushlaydi

4.1 Haqiqiy xabarlar

ESLint II darsida eslint-plugin-react-hooks ni ulagan edik. Uning rules-of-hooks qoidasi bugungi darsning qo'riqchisi. menu.jsx faylida uch xil buzilishni yozib, ESLint 10.12.0 va plagin 7.1.1 bilan tekshirdik:

jsx
import { useState } from "react";

export function Menu({ items }) {
  for (const item of items) {
    useState(item);
  }
  function handleClick() {
    const [x] = useState(0);
  }
  return <button onClick={handleClick}>Menyu</button>;
}

function formatPrice(n) {
  const [c] = useState(n);
  return c;
}

npx eslint menu.jsx chiqishi (har xabar oxiridagi qoida nomi react-hooks/rules-of-hooks qisqartirildi):

text
   5:5   error  React Hook "useState" may be executed more than once. Possibly because it is called in a loop. React Hooks must be called in the exact same order in every component render
   8:17  error  React Hook "useState" is called in function "handleClick" 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"
  14:15  error  React Hook "useState" is called in function "formatPrice" 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"

✖ 3 problems (3 errors, 0 warnings)

Tarjimasi:

  • 5-qator: "useState bir necha marta bajarilishi mumkin. Ehtimol, sikl ichida chaqirilgani uchun."
  • 8-qator: "useState handleClick funksiyasida chaqirilgan. U na komponent, na custom hook. Komponent nomi katta harf bilan, hook nomi use bilan boshlanishi shart."
  • 14-qator: xuddi shunday, formatPrice uchun.

E'tibor bering: ESLint funksiya nomiga qaraydi. formatPrice ni useFormatPrice deb atasangiz, xato yo'qoladi — chunki endi u custom hook. Bu nomni "aldash" uchun emas: ichida haqiqatan hook bo'lgan funksiya hook bo'ladi va hook qoidalari unga ham tegishli.

4.2 TypeScript bu xatoni ko'rmaydi

vazifalar da Sardorning Tajriba.tsx faylini vaqtincha qo'shib, ikkala tekshiruvni ishga tushirdik:

bash
npx eslint src/komponentlar/Tajriba.tsx
npx tsc -b

Natija: ESLint — ✖ 1 problem (1 error, 0 warnings), exit 1. tsc -b — exit 0, jim. TypeScript turlarni tekshiradi, useState ning turi esa if ichida ham, tashqarisida ham bir xil. Qaysi tartibda chaqirilgani — tur emas, xatti-harakat. Shu sababli React loyihasida ESLint ixtiyoriy emas.

Maslahat: vazifalar da npm run lint — --max-warnings=0 bilan (Birinchi React loyiha darsida qo'shdik). rules-of-hooks — error, demak husky va CI uni o'tkazib yubormaydi. Keyingi darslarda tanishadigan exhaustive-deps esa warn darajasida. --max-warnings=0 bo'lmaganda ogohlantirish CI'da jim o'tib ketardi.

5. Shartli hookni to'g'ri yozish

Qoida "shart ishlatma" demaydi. U "shartni hookdan keyin qo'y" deydi. Uchta usul bor.

5.1 Hookni yuqoriga, shartni pastga

Sardorning komponenti. Hook birinchi, erta return — keyin:

tsx
import { useState } from "react";

function EditRow({ editing }: { editing: boolean }) {
  const [text, setText] = useState("Osh"); // har doim chaqiriladi
  if (!editing) {
    return null; // shart — hookdan keyin
  }
  return (
    <input value={text} onChange={(e) => setText(e.target.value)} />
  );
}

export default function App() {
  return (
    <div>
      <EditRow editing={false} />
      <EditRow editing={true} />
    </div>
  );
}

Hech qanday ogohlantirish yo'q. editing false bo'lganda ham useState chaqiriladi — bu isrof emas. Bitta katakni o'qish juda arzon.

5.2 Shartni komponentga aylantirish

Ko'pincha undan ham toza yo'l bor: shart ostidagi qismni alohida komponent qilish. Hook o'sha komponentning yuqori darajasiga ko'chadi, shart esa ota'da qoladi:

jsx
import { useState } from "react";

function NoteField() {
  const [note, setNote] = useState("piyozsiz");
  return <p>Izoh: {note}</p>;
}

function App() {
  const [showNote, setShowNote] = useState(false);
  return (
    <div>
      <p>Osh × 1</p>
      {showNote && <NoteField />}
      <button onClick={() => setShowNote(true)}>Izoh qo'shish</button>
    </div>
  );
}

Shartli komponent — mutlaqo qonuniy. NoteField chizilmasa, uning butun xotirasi o'chadi. Qayta paydo bo'lsa — yangidan boshlanadi. Har komponentning o'z garderobi bor, raqamlar aralashmaydi. Komponent qachon xotirasini saqlashi va qachon yo'qotishi — State'ning saqlanishi va key bilan reset darsida.

5.3 Ro'yxat uchun — massiv bitta state'da

Sikl ichida useState o'rniga — butun ro'yxat bitta state'da (Obyekt va massivlarni immutable yangilash) yoki har element o'z komponentida (.map() + key bilan <CartRow />). Ikkinchi usulda har CartRow ichida o'z useState i bor — bu ham qonuniy, chunki har komponent nusxasi alohida xotira.

Tekshirib ko'ring: items.map((item) => <CartRow key={item.id} item={item} />) — bu yerda ham hook sikl ichida chaqirilmayaptimi? CartRow ichida useState bor-ku.

Javob

Yo'q. .map() hookni emas, elementlarni yaratadi (<CartRow /> — oddiy obyekt). Hook CartRow funksiyasining ichida, uning yuqori darajasida chaqiriladi — va har CartRow nusxasining o'z kataklari bor. Hook chaqiradigan funksiya App emas, CartRow.

6. use() — yagona istisno

React 19 da use nomli funksiya qo'shildi. U ikki narsani o'qiydi: Context (komponentlar daraxti bo'ylab uzatiladigan qiymat — Context I darsida) va Promise (use(): promise va context'ni o'qish darsida). Hozir ularni bilish shart emas — faqat bitta qiziq xususiyatni ko'ramiz. use ni shart ichida chaqirish mumkin:

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

const ThemeContext = createContext("yorug'");

function App() {
  const [showTheme] = useState(true);
  let theme = "—";
  if (showTheme) {
    theme = use(ThemeContext); // ✅ use() — shart ichida ham mumkin
  }
  return <p>Mavzu: {theme}</p>;
}

Ekranda:

text
Mavzu: yorug'

ESLint bu faylda jim. Nega use boshqalardan farq qiladi? U komponent xotirasida katak egallamaydi. U har chaqirilganda qiymatni tashqaridan — Context'dan yoki Promise'dan — yangidan o'qiydi. Garderobga palto topshirmaydi, demak raqamlar siljimaydi. Shuning uchun nomi ham useUse emas, shunchaki use.

Lekin use ham faqat komponent yoki hook ichida chaqiriladi (ikkinchi qoida unga ham tegishli). try/catch ichida esa uni chaqirib bo'lmaydi.

7. Vazifalar o'lchovi: ESLint va TypeScript

Bu dars vazifalar kodini o'zgartirmaydi — kanonda hooklar qoidasi allaqachon bajarilgan. O'rniga bitta tajriba qildik: Sardorning komponentini src/komponentlar/Tajriba.tsx ga vaqtincha qo'yib, ikki tekshiruvchini taqqosladik (haqiqiy chiqish):

text
$ npx eslint src/komponentlar/Tajriba.tsx
  7:27  error  React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?  react-hooks/rules-of-hooks
✖ 1 problem (1 error, 0 warnings)
exit 1

$ npx tsc -b
exit 0

7:27 — 7-qator, 27-ustun: aynan useState so'zi. Keyin fayl o'chirildi. Xulosa vazifalar uchun: npm run check (lint + format + tip + test) hooklar qoidasini ham qo'riqlaydi, tip yolg'iz — yo'q.

Kanondagi hooklarni ham sanadik. Holat 24 da 5 ta komponentda 7 ta useState bor — hammasi funksiya boshida. Keyingi darslarda App ning o'ziga state, effekt va ref'lar qo'shiladi: useRef II darsi oxirida komponentlardagi hook chaqiruvlari 30 dan oshadi. Har birini qo'shganda birinchi savol: "u return dan oldin va hech qanday shart ichida emasmi?"

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Rendered more hooks than during the previous render. if ichidagi hook ochildi hookni yuqoriga, shartni pastga
Rendered fewer hooks than expected. erta return hookdan oldin return ni hamma hooklardan keyinga
change in the order of Hooks + jadval turli shartda turli hooklar jadvaldagi ^^^^ qatoriga qarang
qiymatlar jim almashgan bir xil turdagi hooklar o'rin almashgan ESLint ni yoqing — ish vaqtida xabar bo'lmaydi
is called in function "…" that is neither… oddiy funksiya yoki ishlovchida hook komponentga ko'chiring yoki use… custom hook qiling
may be executed more than once sikl ichida hook massiv — bitta state'da, yoki har element o'z komponentida

Yana bitta tuzoq — nomni aldash. ESLint xatosini yo'qotish uchun formatPrice ni useFormatPrice ga o'zgartirish. Funksiya endi "hook" bo'ldi va uni ham faqat yuqori darajada chaqirish kerak. Agar siz uni .map() ichida chaqirayotgan bo'lsangiz, muammo yo'qolmadi — faqat bir qavat chuqurroqqa ko'chdi.

9. Mashqlar

Mashqlar kurs/mashqlar/17/25-hooklar-qoidasi/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): qaysi biri buzadi?

Har funksiyani o'qing va javob bering: qoida buziladimi?

jsx
function Price({ sale }) {
  const [price] = useState(35000);
  return <p>{sale ? price * 0.9 : price}</p>;
}

function Badge({ isNew }) {
  if (isNew) {
    const [label] = useState("Yangi");
    return <b>{label}</b>;
  }
  return null;
}

function useDiscount() {
  const [percent] = useState(10);
  return percent;
}
  • Price qoidani buzadimi (ha/yo'q):
  • Badge qoidani buzadimi (ha/yo'q):
  • useDiscount qoidani buzadimi (ha/yo'q):
Yechim

Price — hook yuqori darajada, shart faqat qiymatni tanlaydi. Badge — useState if ichida: isNew o'zgarsa, hooklar soni 0 ↔ 1 almashadi. useDiscount — use bilan boshlanadi, demak custom hook; ichidagi useState yuqori darajada.

2-mashq (o'rta): Sardorning tahrir qatorini tuzating

Kod tugma bosilganda yiqiladi. Uni tuzating: useState bitta joyda, tahrir yopiq bo'lsa — Tahrirlash tugmasi, ochiq bo'lsa — maydon va Saqlash tugmasi. Hookni shart ichidan chiqarish uchun «Hookni yuqoriga, shartni pastga» bo'limiga qarang.

jsx
import { useState } from "react";

function App() {
  const [editing, setEditing] = useState(false);
  if (!editing) {
    return (
      <button onClick={() => setEditing(true)}>Tahrirlash</button>
    );
  }
  const [text, setText] = useState("Non olish");
  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={() => setEditing(false)}>Saqlash</button>
    </div>
  );
}

Konsolda:

text
React has detected a change in the order of Hooks called by App. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks
   Previous render            Next render
   ------------------------------------------------------
1. useState                   useState
2. undefined                  useState
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Uncaught Error: Rendered more hooks than during the previous render.
Yechim
jsx
import { useState } from "react";

function App() {
  const [editing, setEditing] = useState(false);
  const [text, setText] = useState("Non olish"); // har doim
  if (!editing) {
    return (
      <button onClick={() => setEditing(true)}>Tahrirlash</button>
    );
  }
  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={() => setEditing(false)}>Saqlash</button>
    </div>
  );
}

Ekranda:

text
Saqlash

Tugma bosildi, maydon va «Saqlash» paydo bo'ldi (maydon qiymati Ekranda ga kirmaydi). text endi tahrir yopiq bo'lganda ham saqlanadi — qayta ochsangiz, oldingi matn turadi. Bu ko'pincha aynan kerakli xatti-harakat.

3-mashq (qiyin): modelga qo'riqchi qo'shing

«O'zimiz yasab ko'ramiz» bo'limidagi modelni kengaytiring: React kabi har chizishda hooklar sonini tekshirsin. render oxirida:

  • agar oldingi chizishda hooklar ko'proq bo'lsa — Error("Rendered fewer hooks than expected.") tashlasin;
  • kamroq bo'lsa (va bu birinchi chizish bo'lmasa) — Error("Rendered more hooks than during the previous render.").

Ishora: oldingi chizishdagi sonni let lastCount = null da saqlang; index chizish oxirida aynan shu chizishdagi hooklar soni. try/catch bilan xatoni ushlab, xato.message ni chiqaring (Xatolarni ushlash).

Yechim
js
const slots = [];
let index = 0;
let lastCount = null; // oldingi chizishdagi hooklar soni
const FEWER = "Rendered fewer hooks than expected.";
const MORE = "Rendered more hooks than during the previous render.";

function useState(initial) {
  const i = index;
  index++;
  if (slots.length <= i) slots.push(initial);
  return [slots[i], (value) => { slots[i] = value; }];
}

function render(component, props) {
  index = 0;
  const result = component(props);
  if (lastCount !== null && index < lastCount) {
    throw new Error(FEWER);
  }
  if (lastCount !== null && index > lastCount) {
    throw new Error(MORE);
  }
  lastCount = index;
  return result;
}

function OrderCard({ showNote }) {
  const [dish] = useState("Osh");
  let note = "";
  if (showNote) {
    [note] = useState("piyozsiz"); // ❌ ataylab
  }
  return `${dish}${note ? ", " + note : ""}`;
}

for (const showNote of [false, false, true]) {
  try {
    console.log(render(OrderCard, { showNote }));
  } catch (xato) {
    console.log(`Xato: ${xato.message}`);
  }
}

Konsolda:

text
Osh
Osh
Xato: Rendered more hooks than during the previous render.

Birinchi chizishda lastCount hali null — tekshiruv yo'q, son eslab qolinadi (1). Ikkinchisida 1 = 1. Uchinchisida 2 > 1 — xato. Haqiqiy React shu tekshiruvni qiladi va qo'shimcha ravishda har katakdagi hook turini solishtiradi — «Haqiqiy React: uch xil natija» bo'limidagi jadval shundan.

10. Real ishda

  • Har React loyihada eslint-plugin-react-hooks bor: Vite shabloni, Next.js (eslint-config-next) — hammasida rules-of-hooks sukut bo'yicha error. Uni o'chirish — kod ko'rigida (code review) darhol savol tug'diradigan narsa.
  • Kod ko'rigida ko'p uchraydigan holat: kimdir tez tuzatish uchun hookni if ichiga qo'yib, ESLint qatorini // eslint-disable-next-line bilan o'chiradi. Bunday qatorni ko'rsangiz — to'xtating.
  • React Compiler (avtomatik optimallashtiruvchi, React Compiler darsida) qoidalar buzilgan komponentni optimallashtirmaydi. Plagin 7 dagi kompilyator qoidalari ham shu sababli recommended ichida.
  • Intervyu savollari: "Nega hookni if ichida chaqirib bo'lmaydi?" (javob: chaqiruv tartibi), "React state'ni qanday qilib to'g'ri komponentga bog'laydi?", "use() nega shart ichida chaqirilishi mumkin?".

Xulosa

  • React hooklarni nomi bo'yicha emas, chaqirilish tartibi bo'yicha eslaydi — har komponentning kataklar ro'yxati bor.
  • Birinchi qoida: hook faqat yuqori darajada — if, sikl, erta return dan keyin, ishlovchi va try ichida emas.
  • Ikkinchi qoida: hook faqat komponent yoki use… custom hook ichida.
  • Buzilish: Rendered more/fewer hooks xatosi, "tartib o'zgardi" jadvali yoki — eng xavflisi — qiymatlarning jim almashishi.
  • Ushlovchi — ESLint react-hooks/rules-of-hooks; TypeScript bu xatoni ko'rmaydi (tsc -b — exit 0).
  • To'g'ri shakl: hook yuqorida, shart pastda yoki shartli qism alohida komponentda. use() — yagona istisno.

Keyingi dars: useEffect I: effekt — tashqi tizim bilan sinxronlash — yangi hook: chizish tugagach ishlaydigan kod va u nega "tashqi tizim" uchun kerak.

Manbalar

  • React: "Rules of Hooks" — react.dev/reference/rules/rules-of-hooks
  • React: use — react.dev/reference/react/use
  • React: "Reusing Logic with Custom Hooks" (use prefiksi) — react.dev/learn/reusing-logic-with-custom-hooks
  • eslint-plugin-react-hooks 7.1.1 — npmjs.com/package/eslint-plugin-react-hooks
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Hooklar qoidalari: nega hook if ichida chaqirilmaydi va React ularni qanday eslaydi — IlmHamroh