IlmHamroh
JavaScript Full-stack/18-qism. React chuqur17/42-dars20 daqiqa
Mundarija (28)

Reconciliation, Virtual DOM va Fiber: React ikki daraxtni qanday solishtiradi

Qisqacha: Render natijasi — DOM emas, oddiy JavaScript obyektlari daraxti ({ type: "p", props: {…} }), ko'pincha "Virtual DOM" deyiladi. Reconciliation (moslashtirish) — React yangi daraxtni eskisi bilan solishtirib, DOM'dagi eng kichik o'zgarishni topishi. U ikki qoidaga tayanadi: turi o'zgargan element — butunlay yangi (eski DOM va state o'chadi), ro'yxatdagi bolalar esa key bo'yicha juftlanadi. Ichkarida har komponent — Fiber degan ish birligi; React ularni sikl bilan bittalab ishlaydi va orada to'xtay oladi. Ro'yxat boshiga bitta taom qo'shilganda indeks-key bilan 4 ta DOM o'zgarishi, key={nomi} bilan — 1 ta: biz buni o'lchadik.

Bu darsda

  • JSX element nima ekanini konsolda ko'rasiz va "Virtual DOM" ni to'g'ri tushunasiz.
  • Diffing'ning ikki qoidasini (tur va key) va ularning oqibatlarini bilasiz.
  • Indeks-key va barqaror key farqini DOM o'zgarishlari soni bilan o'lchaysiz.
  • Fiber daraxtini (child, sibling, return) haqiqiy ilovada aylanib chiqasiz.
  • Work loop va lanes (ustuvorlik) g'oyasini umumiy holda tushuntira olasiz.

Oldin bilishingiz kerak: Render va commit, Ro'yxatlarni render qilish va key, State'ning saqlanishi va key bilan reset, Concurrent rendering va useTransition.

1. Nega bu kerak?

Render va commit darsida «Yangila» tugmasi App ni qayta chizdi, lekin DOM'da 0 ta o'zgarish bo'ldi. «Narx» esa uchta matn tugunidan faqat bittasini almashtirdi. React buni qanday biladi? U qayerdan "faqat shu tugun o'zgardi" degan xulosaga keladi?

Bu savolning javobi kundalik xatolarni tushuntiradi. Nega ro'yxatda key={index} bo'lsa, birinchi qatorga yozilgan matn boshqa qatorga "ko'chib" ketadi (Ro'yxatlar va key)? Nega <div> ni <section> ga almashtirsangiz, ichidagi hisoblagich nolga tushadi (State'ning saqlanishi)? Nega komponent ichida e'lon qilingan komponent har bosishda fokusni yo'qotadi? Uchalasi — bitta mexanizmning oqibati.

Hayotiy o'xshatish: Jasur aka kechagi va bugungi menyu taxtasini solishtiradi. U har harfni solishtirmaydi. Qoidasi oddiy: "bo'lim nomi o'zgarmagan bo'lsa — ichiga qarayman; o'zgargan bo'lsa — butun bo'limni o'chirib, yangidan yozaman". Taomlarni esa nomi bo'yicha juftlaydi, tartib raqami bo'yicha emas. React ham shunday.

2. Element — oddiy obyekt

2.1 JSX nima qaytaradi

JSX I darsida JSX jsx() chaqiruviga aylanishini ko'rgansiz. Chaqiruv natijasini konsolga chiqaramiz:

jsx
function MenuCard({ dish }) {
  return <h2>{dish}</h2>;
}

const price = <p className="narx">35000</p>;
const card = <MenuCard dish="Osh" />;

console.log(typeof price, String(price.$$typeof));
console.log(price.type, JSON.stringify(price.props), price.key);
console.log(card.type === MenuCard, JSON.stringify(card.props));

function App() {
  return price;
}

Konsolda:

text
object Symbol(react.transitional.element)
p {"className":"narx","children":"35000"} null
true {"dish":"Osh"}

React element — "ekranda shu bo'lsin" degan tavsif obyekti. Unda type (teg nomi satr sifatida yoki komponent funksiyasining o'zi), props (atributlar va children) va key bor. $$typeof — React 19 dagi belgi (Symbol), React buni o'z elementi deb taniydi (JSX II darsidagi xavfsizlik tafsiloti). Ikkinchi elementning type i — MenuCard funksiyasining o'zi: React uni hali chaqirmagan.

2.2 "Virtual DOM" — atama haqida

Komponentlar qaytargan elementlar daraxtini ko'pincha Virtual DOM deyishadi: haqiqiy DOM'ning yengil, xotiradagi "nusxasi". Bu atama 2013–2018 yillarda keng tarqaldi, hozirgi react.dev hujjatlari esa uni deyarli ishlatmaydi — "UI daraxti", "render daraxti" deydi.

Bir mifni yo'qotamiz: Virtual DOM DOM'dan "tezroq" emas. Har o'zgarishni qo'lda, aniq DOM chaqiruvi bilan yozgan dastur React'dan tez ishlaydi — React avval solishtirishga vaqt sarflaydi. Virtual DOM'ning foydasi boshqa: siz butun ekranni "holatga qarab" deklarativ yozasiz (React nima), React esa minimal o'zgarishni o'zi topadi. Svelte va Solid kabi kutubxonalar Virtual DOM'siz ishlaydi — ular o'zgarishni kompilyatsiya paytida aniqlaydi.

3. Diffing: ikki qoida

3.1 Nega qoida kerak

Ikki ixtiyoriy daraxtni solishtirib, eng kam amal bilan birini ikkinchisiga aylantirish — og'ir masala: ma'lum algoritmlar n ta element uchun taxminan n³ amal qiladi. 1 000 elementda — milliard amal, har bosishda. React bunday qilmaydi. U ikki taxminga tayanib, ishni n ga tushiradi (har element bir marta ko'riladi):

  1. Turi boshqa element — boshqa daraxt. <div> o'rniga <section> yoki Header o'rniga Footer kelsa, React ichiga qaramaydi: eskisini butunlay o'chiradi, yangisini noldan yaratadi.
  2. key — bolalarning ismi. Bir ota ichidagi bolalar key bo'yicha juftlanadi; key bo'lmasa — tartib raqami (indeks) bo'yicha.

Taxmin ba'zan "noto'g'ri" bo'lishi mumkin (masalan, <div> → <section> ichidagi hamma narsa aslida o'sha). Lekin amalda bunday holat kam, tezlik esa ming barobar farq qiladi.

flowchart TD
  A["Eski va yangi element"] --> B{"type bir xilmi?"}
  B -->|"yo'q"| C["Eskisini o'chir, yangisini yarat"]
  B -->|"ha"| D["DOM va state saqlanadi"]
  D --> E["props farqini DOM'ga yoz"]
  E --> F["Bolalarni key bo'yicha juftla"]
  F --> A

Diagrammadagi halqaga qarang: bolalar uchun ham xuddi shu savol beriladi — daraxt pastga qarab bir marta aylanib chiqiladi.

3.2 Turi o'zgarsa — hammasi yangidan

State'ning saqlanishi va key bilan reset darsidagi qoida aynan shu: state komponentga emas, daraxtdagi o'rniga bog'langan. O'rin o'sha, tur boshqa — eski komponent "o'ladi". Bitta klassik tuzoq bunga yangi rang qo'shadi — komponent ichida e'lon qilingan komponent:

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

function App() {
  const [tick, setTick] = useState(0);

  function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
      console.log("Counter yaratildi");
    }, []);
    return <button onClick={() => setCount(count + 1)}>+1</button>;
  }

  return (
    <div>
      <Counter />
      <button onClick={() => setTick(tick + 1)}>Yangila</button>
    </div>
  );
}

Konsolda:

text
Counter yaratildi
Counter yaratildi

Ikkinchi qator — «Yangila» dan keyin. App qayta chizilganda function Counter yangidan e'lon qilinadi — bu yangi funksiya obyekti. React uchun type o'zgardi: oldingiCounter !== yangiCounter. Birinchi qoida ishladi — eski Counter o'chirildi (state bilan birga), yangisi yaratildi. Agar ichida input bo'lsa, har renderda fokus va yozilgan matn yo'qolardi. Davosi — komponentni modul darajasida, tashqarida e'lon qilish. ESLint buni react-hooks/static-components qoidasi bilan ushlaydi (React Compiler darsida bu qoidalar to'plamini ko'ramiz) — lazy darsida vazifalar da aynan shu xato topilgan edi.

Tekshirib ko'ring: {isAdmin ? <Panel /> : <Panel />} — isAdmin o'zgarsa, Panel ning state'i saqlanadimi?

Javob

Saqlanadi. Ikkala shoxda ham o'sha joyda o'sha type (Panel) turibdi — React uchun bu bir komponent, faqat props boshqa bo'lishi mumkin. Agar ataylab qayta yaratish kerak bo'lsa — har shoxga boshqa key bering: <Panel key="admin" /> va <Panel key="mehmon" /> (State'ning saqlanishi).

4. Key: bolalarni juftlash

4.1 Indeks-key bilan

Endi ikkinchi qoida — o'lchov bilan. Uch taomli ro'yxat, «Boshiga qo'sh» — boshiga "Lag'mon" qo'shadi. Render va commit dagi MutationObserver usuli bilan DOM o'zgarishlarini sanaymiz. Avval key={i}:

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

const observer = new MutationObserver(() => {});

function App() {
  const [dishes, setDishes] = useState(["Osh", "Manti", "Somsa"]);
  const box = useRef(null);
  useLayoutEffect(() => {
    observer.observe(box.current, {
      subtree: true,
      childList: true,
      characterData: true,
    });
    return () => observer.disconnect();
  }, []);
  useLayoutEffect(() => {
    const types = observer.takeRecords().map((r) => r.type);
    const list = types.join(", ") || "—";
    console.log("o'zgarish:", types.length, list);
  });
  function addFirst() {
    setDishes(["Lag'mon", ...dishes]);
  }
  return (
    <div>
      <button onClick={addFirst}>Boshiga qo'sh</button>
      <ul ref={box}>
        {dishes.map((d, i) => (
          <li key={i}>{d}</li>
        ))}
      </ul>
    </div>
  );
}

Konsolning ikkinchi qatoriga qarang: bitta taom qo'shildi, lekin o'zgarishlar soni nechta?

Konsolda:

text
o'zgarish: 0 —
o'zgarish: 4 characterData, characterData, characterData, childList

React ro'yxatlarni key bo'yicha juftladi: eski key=0 ("Osh") ↔ yangi key=0 ("Lag'mon"). Turi bir xil (li), demak element saqlanadi, faqat matn o'zgaradi. Shunday qilib uchta mavjud <li> ning uchalasida matn almashdi (3 × characterData), oxiriga esa yangi <li> qo'shildi (childList, matni "Somsa").

key Eski matn Yangi matn DOM'da
0 Osh Lag'mon matn almashdi
1 Manti Osh matn almashdi
2 Somsa Manti matn almashdi
3 — Somsa yangi <li>

4.2 Barqaror key bilan

Bitta o'zgarish — key={d} (taom nomi takrorlanmaydi deb hisoblaymiz):

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

const observer = new MutationObserver(() => {});

function App() {
  const [dishes, setDishes] = useState(["Osh", "Manti", "Somsa"]);
  const box = useRef(null);
  useLayoutEffect(() => {
    observer.observe(box.current, {
      subtree: true,
      childList: true,
      characterData: true,
    });
    return () => observer.disconnect();
  }, []);
  useLayoutEffect(() => {
    const types = observer.takeRecords().map((r) => r.type);
    const list = types.join(", ") || "—";
    console.log("o'zgarish:", types.length, list);
  });
  function addFirst() {
    setDishes(["Lag'mon", ...dishes]);
  }
  return (
    <div>
      <button onClick={addFirst}>Boshiga qo'sh</button>
      <ul ref={box}>
        {dishes.map((d) => (
          <li key={d}>{d}</li>
        ))}
      </ul>
    </div>
  );
}

Konsolda:

text
o'zgarish: 0 —
o'zgarish: 1 childList

Endi React "Osh", "Manti", "Somsa" ni o'z key lari bo'yicha topdi — ular o'zgarmagan. Faqat key="Lag'mon" yangi: bitta <li> yaratilib, ro'yxat boshiga qo'yildi. Bitta DOM amali.

key={i} key={d}
DOM o'zgarishi 4 (3 matn + 1 element) 1 (1 element)
eski <li> lar o'sha, lekin boshqa matn bilan o'sha, o'sha matn bilan
<li> ichida input bo'lsa yozilgan matn boshqa taomga "ko'chadi" to'g'ri joyida

Uchinchi qator — eng xavflisi. Ro'yxat qatorida input, katak yoki ochiq menyu bo'lsa, indeks-key bilan ularning holati boshqa ma'lumotga yopishib qoladi: React DOM elementini saqlaydi, lekin unga boshqa taomni yozadi. Ma'lumotda barqaror id bo'lsa — doim shuni ishlating. vazifalar da aynan shunday: key={vazifa.id}.

Tekshirib ko'ring: Ro'yxat oxiriga taom qo'shilsa, key={i} bilan nechta DOM o'zgarishi bo'ladi?

Javob

Bitta. Eski key=0,1,2 yangi ro'yxatda ham o'sha taomlarga to'g'ri keladi — matn o'zgarmaydi, faqat key=3 yangi. Indeks-key faqat boshiga qo'shish, o'rtadan o'chirish va saralash da muammo beradi. Shuning uchun xato ko'pincha yashirin qoladi: sinovda faqat oxiriga qo'shiladi.

4.3 vazifalar dagi key

Kanondagi ToliqRoyxat (holat 23) izohida shu dars yozilgan:

tsx
function ToliqRoyxat({ vazifalar, sorov, tahrirId }: Props) {
  // key — vazifa id: barqaror va takrorlanmaydi. Tartib
  // o'zgarsa (saralash, bajarilgan pastga tushdi) React <li> ni
  // yangidan yasamaydi, joyini almashtiradi — fokus va yozilgan
  // matn saqlanadi
  return vazifalar.map((vazifa) => (
    <VazifaQatori
      key={vazifa.id}

Kanon izohi telefonga sig'ishi uchun qayta bo'lindi, matni o'sha. Bajarilgan vazifa ro'yxat pastiga tushadi (Mini-ilova). key={vazifa.id} bilan React o'sha <li> ni ko'chiradi — Bobur Space bosgan katakda fokus qoladi (Fokus boshqaruvi darsida e2e tekshiruvi).

Birinchi qoidaning ham vazifalar dagi izi bor. Holat 23 da VazifaRoyxati vazifalar soniga qarab ikki xil komponent qaytaradi: 200 tagacha — <ToliqRoyxat>, ko'p bo'lsa — <VirtualRoyxat> (Virtualizatsiya). Shu chegaradan o'tilganda o'sha joyda type o'zgaradi — React eski ro'yxatni butunlay o'chirib, yangisini yaratadi. Bu ataylab: ikki xil hooklar to'plami bitta komponentda shart bilan chaqirilmaydi (Hooklar qoidalari), chegaradan o'tish esa kam bo'ladi.

5. Fiber: React'ning ish daftari

5.1 Element — bir martalik, Fiber — doimiy

Element har renderda yangidan yasaladi va keyin tashlanadi. React esa renderlar orasida ko'p narsani eslab qolishi kerak: state, effektlar, DOM tuguni, "kim kimning otasi". Buning uchun u har komponent va har DOM elementi uchun Fiber obyektini saqlaydi. Fiber (tola) — React 16 dan (2017) beri ichki tuzilma: bitta daraxt tuguni va shu bilan birga bitta ish birligi (unit of work).

Fiber'ning asosiy maydonlari:

Maydon Nima
type, key elementdan: teg yoki komponent funksiyasi
child, sibling, return birinchi bola, keyingi aka-uka, ota
memoizedState, memoizedProps oxirgi commit'dagi state (hooklar ro'yxati) va props
stateNode DOM tuguni (teg uchun)
alternate shu Fiber'ning ikkinchi nusxasi
flags, lanes nima qilish kerak va qanchalik shoshilinch

5.2 Haqiqiy Fiber daraxtini aylanamiz

Fiber — ichki tuzilma, hujjatlanmagan va versiyadan versiyaga o'zgaradi. Lekin React DevTools uni o'qiydi, biz ham o'rganish uchun o'qiymiz. react-dom har DOM tuguniga __reactFiber$… nomli xususiyat qo'shadi (oxiri tasodifiy belgilar):

jsx
import { useLayoutEffect } from "react";

function fiberOf(node) {
  const key = Object.keys(node).find((k) =>
    k.startsWith("__reactFiber$"),
  );
  return node[key];
}

function nameOf(fiber) {
  if (typeof fiber.type === "string") return `<${fiber.type}>`;
  if (typeof fiber.type === "function") return fiber.type.name;
  return `(tag ${fiber.tag})`;
}

function Header() {
  return <h1>«Bahor»</h1>;
}

function Dish({ title }) {
  return <li id={title}>{title}</li>;
}

function App() {
  useLayoutEffect(() => {
    const path = [];
    let f = fiberOf(document.getElementById("Manti"));
    while (f) {
      path.push(nameOf(f));
      f = f.return;
    }
    console.log("return:", path.join(" → "));
    const list = fiberOf(document.querySelector("ul"));
    const kids = [];
    for (let c = list.child; c; c = c.sibling) kids.push(nameOf(c));
    console.log("<ul> bolalari:", kids.join(", "));
  }, []);
  return (
    <main>
      <Header />
      <ul>
        <Dish title="Osh" />
        <Dish title="Manti" />
      </ul>
    </main>
  );
}

Konsolda:

text
return: <li> → Dish → <ul> → <main> → App → (tag 3)
<ul> bolalari: Dish, Dish

Birinchi qator — "Manti" <li> idan yuqoriga, return (ota) bo'ylab. Dish — alohida tugun: komponentning o'zi ham Fiber, uning ichida <li>. Eng yuqoridagi (tag 3) — HostRoot, createRoot yaratgan ildiz. Ikkinchi qator — <ul> ning bolalari: child (birinchisi), keyin sibling zanjiri. Bu daraxt rasmda:

flowchart TD
  R["HostRoot"] -->|"child"| A["App"]
  A -->|"child"| M["main"]
  M -->|"child"| H["Header"]
  H -->|"sibling"| U["ul"]
  H -->|"child"| H1["h1"]
  U -->|"child"| D1["Dish: Osh"]
  D1 -->|"sibling"| D2["Dish: Manti"]
  D1 -->|"child"| L1["li"]
  D2 -->|"child"| L2["li"]

Strelkalarga qarang: ota faqat birinchi bolasini biladi (child), qolganlari zanjir (sibling). Bu massiv emas — bog'langan ro'yxat. Nega shunday — keyingi bo'limda. vazifalar dagi render o'lchov skripti (Render va commit dagi onCommitFiberRoot) aynan shu maydonlar bo'yicha yurgan edi: root.current → child → sibling.

Diqqat: __reactFiber$ — o'rganish va o'lchov vositalari uchun. Ilova kodida unga tayanmang: React yangilanishida u ogohlantirishsiz o'zgarishi mumkin. Komponent ma'lumoti kerak bo'lsa — props, context yoki ref.

5.3 Ikki daraxt: current va alternate

React bir vaqtda ikki Fiber daraxtini saqlaydi. current — ekrandagi holat (oxirgi commit). work-in-progress — hozir hisoblanayotgan yangi holat. Har Fiber'ning alternate i — ikkinchi daraxtdagi jufti. Commit — oddiy almashtirish: ildizning current ko'rsatkichi yangi daraxtga o'tadi, eskisi keyingi render uchun qoralama bo'ladi.

Bu double buffering (ikki buferli chizish) — o'yinlardagi usul: keyingi kadr ko'rinmas buferda chiziladi, tayyor bo'lgach bir lahzada almashtiriladi. Shuning uchun ekranda yarim tayyor holat ko'rinmaydi, tashlab yuborilgan render esa hech narsani buzmaydi — u faqat qoralamada edi.

6. Work loop va lanes

6.1 Ish sikli

Fiber'lar bog'langan ro'yxat bo'lgani uchun React daraxtni rekursiyasiz, oddiy sikl bilan aylana oladi. Soddalashtirilgan ko'rinishi (React kodining g'oyasi, aynan kodi emas):

js
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress); // bitta Fiber
  }
}

function performUnitOfWork(fiber) {
  // komponentni chaqir, bolalarni solishtir
  const next = beginWork(fiber);
  if (next !== null) {
    workInProgress = next; // pastga: child
  } else {
    completeUnitOfWork(fiber); // sibling yoki yuqoriga: return
  }
}

Work loop (ish sikli) — "keyingi Fiber'ni ol, ishla, keyingisiga o't". beginWork — komponent funksiyasini chaqiradi va yangi bolalarni eskilari bilan solishtiradi (reconciliation shu yerda). Bola bo'lmasa — sibling ga, u ham bo'lmasa — return bo'ylab yuqoriga. Rekursiya bo'lsa, chaqiruvlar stekini o'rtada to'xtatib bo'lmasdi. Sikl esa istalgan Fiber'dan keyin to'xtay oladi va keyinroq workInProgress dan davom etadi.

shouldYield() — Concurrent rendering darsidagi "5 ms o'tdimi, navbatda bosish bormi?" savoli. Transition renderi aynan shu siklda bo'laklarga bo'linadi. Shoshilinch render esa shouldYield siz siklda (workLoopSync) — oxirigacha.

6.2 Lanes: ustuvorlik

Lane (yo'lak) — yangilanishning ustuvorligi, bitlar bilan yozilgan son. Har yangilanish o'z yo'lagiga tushadi; bir nechta yo'lak bitta songa | bilan yig'iladi (Bit amallari). React avval eng muhim yo'lakni ishlaydi. Natija oynasida ildizdagi kutilayotgan yo'laklarni o'qiymiz. pendingLanes — yana ichki maydon: yuqoridagi «Diqqat» bloki bu yerda ham amal qiladi, faqat o'rganish uchun.

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

function pendingLanes() {
  const el = document.getElementById("root");
  const key = Object.keys(el).find((k) =>
    k.startsWith("__reactContainer$"),
  );
  return el[key].stateNode.pendingLanes;
}

function App() {
  const [tab, setTab] = useState("bosh");
  function openMenu() {
    startTransition(() => setTab("taomlar"));
    console.log("transition:", pendingLanes());
  }
  function openContact() {
    setTab("aloqa");
    console.log("bosish:", pendingLanes());
  }
  return (
    <div>
      <button onClick={openMenu}>Taomlar</button>
      <button onClick={openContact}>Aloqa</button>
      <p>{tab}</p>
    </div>
  );
}

Konsolda:

text
transition: 256
bosish: 2

Ekranda:

text
TaomlarAloqa
aloqa

2 — SyncLane: bosish kabi diskret hodisadagi yangilanish, eng shoshilinch. 256 — birinchi transition yo'lagi. React 19.3.0 kodida (react-dom) yana bir nechtasi bor. 8 — uzluksiz hodisa (scroll, sichqoncha harakati), 32 — sukut (masalan setTimeout ichidagi set…). Transition'lar uchun 256 dan boshlab 14 ta yo'lak, eng oxirida — Idle. Son kichik — ustuvorlik yuqori.

Bu bilim kundalik kod uchun shart emas — siz yo'lakni hech qachon qo'lda tanlamaysiz. Lekin "transition nega kutadi" ning javobi shu: 256 yo'lagidagi ish 2 kelganda chetga suriladi. Profiler va Performance panelidagi "Blocking" va "Transition" treklari ham shu yo'laklar guruhlari (Performance'ni o'lchash).

Tekshirib ko'ring: Nega Fiber daraxti massivlar emas, child/sibling/return ko'rsatkichlari bilan qurilgan?

Javob

To'xtab, keyin davom etish uchun. Ko'rsatkichlar bilan "hozir qayerdaman" — bitta o'zgaruvchi (workInProgress). Undan keyingi qadam doim ma'lum: child, bo'lmasa sibling, bo'lmasa return. Rekursiv aylanishda esa holat JavaScript chaqiruvlar stekida — uni saqlab qo'yib, 5 ms dan keyin qaytib bo'lmaydi.

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
ro'yxatdagi input matni boshqa qatorga o'tdi key={index}, boshiga qo'shish yoki saralash key={item.id}
har renderda fokus va state yo'qoladi komponent boshqa komponent ichida e'lon qilingan modul darajasiga chiqaring
<div> ni <section> qilsam, state nolga tushdi o'sha joyda type o'zgardi o'ram turini saqlang yoki state'ni ko'taring
key={Math.random()} — hammasi sekin, fokus yo'q har renderda yangi key — har qator qayta yaratiladi barqaror id
Each child in a list should have a unique "key" prop. map da key yo'q ma'lumotdagi id (Ro'yxatlar va key)
__reactFiber$ ga tayangan kod yangilanishda buzildi ichki API props, ref, context

8. Mashqlar

Mashqlar kurs/mashqlar/18/17-reconciliation/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): saqlanadimi?

Har o'zgarishda Counter ning state'i saqlanadimi?

  • <div><Counter /></div> → <div className="faol"><Counter /></div>:
  • <div><Counter /></div> → <section><Counter /></section>:
  • <Counter key="osh" /> → <Counter key="manti" />:
  • <Counter color="red" /> → <Counter color="green" />:
Yechim

Birinchisida div o'sha — faqat atribut o'zgardi (props farqi). Ikkinchisida ota type i o'zgardi — butun ichi qayta yaratiladi. Uchinchisida key o'zgardi — React buni boshqa komponent deb biladi. To'rtinchisida — o'sha tur, o'sha joy, faqat props: state saqlanadi. Qoida: "turi va key o'sha bo'lsa — saqlanadi".

2-mashq (o'rta): saralashdagi tuzoq

Buyurtmalar ro'yxatida har qatorda izoh maydoni bor. Mehmon Osh izohiga "achchiq" deb yozdi, keyin «Narx bo'yicha» ni bosdi. Tartib o'zgardi — va izoh boshqa taomga ko'chib ketdi. Faqat bitta joyni o'zgartirib tuzating. Tekshiruvchi Osh maydoniga yozadi, «Narx bo'yicha» ni bosadi, keyin «Ko'rsat» har qatordagi taomni va uning maydonidagi matnni konsolga chiqaradi.

jsx
import { useState } from "react";

const ORDERS = [
  { id: 1, dish: "Osh", price: 35000 },
  { id: 2, dish: "Lag'mon", price: 28000 },
  { id: 3, dish: "Manti", price: 30000 },
];

function App() {
  const [sorted, setSorted] = useState(false);
  const list = sorted
    ? ORDERS.toSorted((a, b) => a.price - b.price)
    : ORDERS;
  function show() {
    for (const li of document.querySelectorAll("li")) {
      const note = li.querySelector("input").value;
      console.log(li.dataset.dish, "→", note);
    }
  }
  return (
    <div>
      <button onClick={() => setSorted(true)}>Narx bo'yicha</button>
      <button onClick={show}>Ko'rsat</button>
      <ul>
        {list.map((o, i) => (
          <li key={i} data-dish={o.dish}>
            {o.dish} <input aria-label={`${o.dish} izohi`} />
          </li>
        ))}
      </ul>
    </div>
  );
}

Konsolda:

text
Lag'mon → achchiq
Manti → 
Osh → 
Yechim
jsx
import { useState } from "react";

const ORDERS = [
  { id: 1, dish: "Osh", price: 35000 },
  { id: 2, dish: "Lag'mon", price: 28000 },
  { id: 3, dish: "Manti", price: 30000 },
];

function App() {
  const [sorted, setSorted] = useState(false);
  const list = sorted
    ? ORDERS.toSorted((a, b) => a.price - b.price)
    : ORDERS;
  function show() {
    for (const li of document.querySelectorAll("li")) {
      const note = li.querySelector("input").value;
      console.log(li.dataset.dish, "→", note);
    }
  }
  return (
    <div>
      <button onClick={() => setSorted(true)}>Narx bo'yicha</button>
      <button onClick={show}>Ko'rsat</button>
      <ul>
        {list.map((o) => (
          <li key={o.id} data-dish={o.dish}>
            {o.dish} <input aria-label={`${o.dish} izohi`} />
          </li>
        ))}
      </ul>
    </div>
  );
}

Konsolda:

text
Lag'mon → 
Manti → 
Osh → achchiq

Indeks-key bilan React birinchi <li> ni (ichidagi input bilan) saqladi va unga "Lag'mon" matnini yozdi. Input esa uncontrolled — uning qiymati React state'ida emas, DOM'da (Formalar II). Shuning uchun "achchiq" o'z joyida, endi Lag'mon yonida qoldi. key={o.id} bilan React <li> larni ko'chiradi: "achchiq" li input Osh bilan birga pastga tushdi. toSorted — asl massivni o'zgartirmaydigan saralash (Nusxa bilan o'zgartirish: toSorted).

3-mashq (qiyin): bir xil tur, boshqa key

Taom kartasi (DishCard) ichida "Sevimli" belgisi (state) bor. Mehmon taomni almashtirsa (Osh → Manti), belgi yangi taomga ham o'tib qolyapti. DishCard ichini o'zgartirmasdan, App da bitta atribut bilan tuzating: taom almashsa, karta noldan boshlansin. Kutilgan konsol: Oshni sevimli qilib, Mantiga o'tilgandan keyin Manti: sevimli emas.

jsx
import { useState } from "react";

function DishCard({ dish }) {
  const [liked, setLiked] = useState(false);
  console.log(`${dish}: ${liked ? "sevimli" : "sevimli emas"}`);
  return (
    <div>
      <h2>{dish}</h2>
      <button onClick={() => setLiked(true)}>Sevimli</button>
    </div>
  );
}

function App() {
  const [dish, setDish] = useState("Osh");
  return (
    <div>
      <button onClick={() => setDish("Manti")}>Manti</button>
      <DishCard dish={dish} />
    </div>
  );
}
Yechim
jsx
import { useState } from "react";

function DishCard({ dish }) {
  const [liked, setLiked] = useState(false);
  console.log(`${dish}: ${liked ? "sevimli" : "sevimli emas"}`);
  return (
    <div>
      <h2>{dish}</h2>
      <button onClick={() => setLiked(true)}>Sevimli</button>
    </div>
  );
}

function App() {
  const [dish, setDish] = useState("Osh");
  return (
    <div>
      <button onClick={() => setDish("Manti")}>Manti</button>
      <DishCard key={dish} dish={dish} />
    </div>
  );
}

Konsolda:

text
Osh: sevimli emas
Osh: sevimli
Manti: sevimli emas

key={dish} — "har taom — alohida karta". Osh dan Manti ga o'tganda key o'zgardi, React eski DishCard Fiber'ini (state bilan) o'chirib, yangisini yaratdi. Bu props o'zgarganda state'ni effekt bilan "tozalash" dan yaxshiroq: bitta render, oraliq noto'g'ri holat yo'q (Sizga effekt kerak emas).

4-mashq: vazifalar da key'ni buzib ko'ring

vazifalar ning vaqtinchalik nusxasida ToliqRoyxat dagi key={vazifa.id} ni key={i} ga almashtiring (map((vazifa, i) => …)). npm run dev qiling va uchta vazifa yarating. Ikkinchisini tahrirlashni boshlang (matnni o'zgartiring, saqlamang), keyin boshqa tabda birinchisini bajarilgan qiling. Nima bo'ladi? Keyin o'zgarishni qaytaring — commit qilmang.

Yechim

Birinchi vazifa bajarilganda u pastga tushadi — tartib o'zgaradi. Indeks-key bilan tahrir qatorining DOM'i o'sha o'rinda qoladi — TahrirQatori ning state'i va maydondagi yozilgan matn bilan birga. Lekin o'sha o'ringa endi boshqa vazifa keladi: yozilgan matn boshqa vazifaga "o'tadi" yoki tahrirda belgisi bilan mos kelmay qoladi. key={vazifa.id} bilan React qatorni ko'chiradi — tahrir o'sha vazifada. Shuning uchun kanon izohi "fokus va yozilgan matn saqlanadi" deydi.

9. Real ishda

  • Ro'yxatlar: jadval, chat, kanban — hamma joyda key = ma'lumotdagi id. Server id bermasa (yangi, hali saqlanmagan element) — crypto.randomUUID() bilan bir marta, element yaratilganda (Mini-ilova).
  • Forma reset: "boshqa foydalanuvchi profilini ochdi — forma tozalansin" — key={userId}.
  • Kutubxonalar: React DevTools, react-scan va testing kutubxonalari Fiber daraxtini o'qiydi; siz ularni ishlatasiz, Fiber'ni emas.
  • Intervyu savollari: "Virtual DOM nima va u tezmi?", "Reconciliation qanday ishlaydi?", "Nega key={index} yomon?", "Fiber nima uchun kerak bo'ldi?", "React render'ni qanday to'xtatadi?".

Xulosa

  • Render natijasi — element obyektlari (type, props, key); "Virtual DOM" — shu daraxt, u DOM'dan tez emas, deklarativ yozishni ta'minlaydi.
  • Diffing ikki qoida bilan O(n): turi boshqa — butun daraxt qayta yaratiladi (state yo'qoladi); bolalar key bo'yicha juftlanadi.
  • O'lchov: boshiga qo'shishda key={i} — 4 DOM o'zgarishi, key={nomi} — 1; indeks-key holatni boshqa ma'lumotga "yopishtiradi".
  • Komponent ichida e'lon qilingan komponent — har renderda yangi tur, demak har renderda qayta yaratiladi.
  • Fiber — doimiy ish birligi (child/sibling/return, alternate); work loop uni sikl bilan ishlaydi va shouldYield da to'xtaydi; lanes — ustuvorlik (bosish 2, transition 256).

Keyingi dars: Qayta render sabablari va miflar — komponent aynan nima uchun qayta chiziladi va "props o'zgardi" nega sabab emas.

Manbalar

  • React: "Preserving and Resetting State" — react.dev/learn/preserving-and-resetting-state
  • React (legacy): "Reconciliation" — legacy.reactjs.org/docs/reconciliation.html
  • React Fiber Architecture (Andrew Clark, 2016) — github.com/acdlite/react-fiber-architecture
  • react-dom 19.3.0 manba kodi: getHighestPriorityLanes, DiscreteEventPriority = 2, DefaultEventPriority = 32 — npm paketi
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Reconciliation, Virtual DOM va Fiber: React ikki daraxtni qanday solishtiradi — IlmHamroh