IlmHamroh
JavaScript Full-stack/18-qism. React chuqur16/42-dars18 daqiqa
Mundarija (31)

Render va commit: React ekranni qanday yangilaydi

Qisqacha: React ekranni to'rt bosqichda yangilaydi: trigger (setState yoki birinchi root.render), render (React komponentlaringizni chaqirib, ekran qanday bo'lishi kerakligini hisoblaydi), commit (faqat farqni DOM'ga yozadi). Oxirida paint — brauzer piksellarni chizadi. Render — hisob-kitob, u DOM'ga tegmaydi va tashlab yuborilishi ham mumkin. Shuning uchun "qayta render" ≠ "DOM qayta chizildi": tugma bosildi, komponent qayta chizildi, lekin natija o'sha bo'lsa — DOM'da 0 ta o'zgarish. Buni MutationObserver bilan o'lchab ko'ramiz.

Bu darsda

  • Trigger, render, commit va paint bosqichlarini konsol loglarida ajrata olasiz.
  • Nega render DOM o'zgarishini anglatmasligini o'lchov bilan ko'rsatasiz.
  • Render commit'siz bo'lishi mumkinligini (StrictMode, transition) tushuntirasiz.
  • Commit ichidagi tartibni bilasiz: DOM → ref → useLayoutEffect → paint → useEffect.
  • vazifalar da "render soni" va "commit soni" qanday o'lchanganini o'qiysiz.

Oldin bilishingiz kerak: Pure komponentlar va render qoidasi, State II: snapshot va batching, useEffect I, useLayoutEffect, Concurrent rendering va useTransition.

1. Nega bu kerak?

Oldingi darslarda "qayta chizish", "re-render", "commit" so'zlarini ko'p ishlatdik. Concurrent rendering darsida esa g'alati narsani ko'rgan edik: konsolda App: taomlar bor, lekin "Taomlar" ekranga hech qachon chiqmadi. React komponentni chaqirdi — va natijani tashlab yubordi.

Endi 18-qismning performans bo'limi boshlanadi. Keyingi to'qqiz dars "nega sekin" va "qanday tezlatish" haqida. Ularning hammasi bitta savolga tayanadi: React aslida qanday ish qiladi va qayerda vaqt sarflanadi? Agar "render" ni "DOM'ni qayta yasash" deb tushunsangiz, noto'g'ri joyni optimallashtirasiz.

Hayotiy o'xshatish — «Bahor» oshxonasi. Mehmon buyurtma berdi (trigger). Oshpaz oshxonada taomni tayyorlaydi (render) — mehmon buni ko'rmaydi, oshpaz xohlasa tashlab, qaytadan boshlashi ham mumkin. Ofitsiant tayyor taomni stolga qo'yadi (commit) — faqat o'zgargan narsani: choynak joyida tursa, uni qayta olib kelmaydi. Mehmon ko'radi (paint).

2. To'rt bosqich

2.1 Umumiy rasm

sequenceDiagram
  participant F as Foydalanuvchi
  participant R as React
  participant K as Komponent
  participant D as DOM
  participant B as Brauzer
  F->>R: bosish → setCount(1)
  Note over R: trigger
  R->>K: App() ni chaqiradi
  K-->>R: JSX (yangi tavsif)
  Note over R,K: render
  R->>D: faqat farqni yozadi
  Note over R,D: commit
  D->>B: piksellar
  Note over B: paint

Diagrammadagi chiziqlarga qarang: komponent (K) DOM'ga umuman tegmaydi. U faqat React'ga "ekran shunday bo'lsin" degan tavsifni qaytaradi. DOM bilan faqat React gaplashadi va faqat commit bosqichida.

2.2 Trigger — render nimadan boshlanadi

Render ikki sababdan boshlanadi:

  1. Birinchi chizish — createRoot(el).render(<App />) (Birinchi React loyiha). React butun daraxtni noldan chizadi.
  2. Holat o'zgarishi — setState, dispatch, context qiymati o'zgarishi, useSyncExternalStore dagi tashqi manba o'zgarishi. React shu komponentni (va uning ostidagilarni) qayta chizishni navbatga qo'yadi.

Bitta hodisa ichidagi bir nechta set… — bitta render: bu batching (State II). Trigger "hoziroq chiz" degani emas, "chizish kerak" degan belgi.

Ikki trigger natijasi ham farq qiladi. Birinchi chizishda (mount — o'rnatish) DOM'da hali hech narsa yo'q: React har elementni yaratadi va butun daraxtni bir martada #root ga qo'shadi. Qayta chizishda (update — yangilash) DOM allaqachon bor, React faqat uni to'g'rilaydi. Shuning uchun birinchi chizish doim qimmatroq: vazifalar da 10 000 vazifa bilan sahifa ochilishi ~760 ms edi (Virtualizatsiya darsidagi o'lchov), bitta katakni bosish esa 264 ms. Profiler'da ham bu ikki so'z uchraydi — "mount" va "update" (Performance'ni o'lchash).

2.3 Render — hisob-kitob

Render (chizish bosqichi) — React komponent funksiyasini chaqiradi va qaytgan JSX'ni oladi. Funksiya ichidagi hamma narsa shu yerda bajariladi: hisoblar, console.log, map. Natija — oddiy JavaScript obyektlari: "bu yerda <p>, ichida 0". DOM'da hali hech narsa o'zgarmagan.

Shuning uchun Pure komponentlar qoidasi bor edi: render toza hisob bo'lsin, DOM'ga, tarmoqqa, global o'zgaruvchiga tegmasin. Tashqariga tegsa — commit bo'lmagan render ham iz qoldiradi.

2.4 Commit va paint

Commit (qayd etish bosqichi) — React yangi tavsifni oldingisi bilan solishtiradi va DOM'ga faqat farqni yozadi: bitta matnni almashtirish, bitta atributni o'zgartirish, bitta <li> qo'shish. Commit doim bir martada, oxirigacha bajariladi — ekranda yarim yangilangan holat ko'rinmaydi.

Paint (bo'yash) — brauzerning ishi: o'zgargan DOM bo'yicha uslublarni hisoblaydi, joylashuvni (layout) va piksellarni chizadi. React bu bosqichni boshqarmaydi, faqat kutadi. useEffect lar odatda shundan keyin ishlaydi (useEffect I).

Tekshirib ko'ring: Komponent ichida document.title = "Osh" yozilgan (effektsiz, to'g'ridan-to'g'ri render ichida). Bu qaysi bosqichda bajariladi va nega xavfli?

Javob

Render bosqichida — funksiya chaqirilganda. Xavfli, chunki render commit bilan tugashi shart emas: React uni tashlab yuborishi yoki (StrictMode'da) ikki marta chaqirishi mumkin. Sarlavha esa ekranga chiqmagan holat bo'yicha o'zgarib qoladi. To'g'ri joy — effekt, u faqat commit'dan keyin ishlaydi.

3. Bosqichlar konsolda

3.1 Uch log

Har bosqichga bittadan log qo'yamiz. Render — funksiya tanasida. Commit tugagani — useLayoutEffect da: u DOM yangilangandan keyin, lekin brauzer bo'yashidan oldin ishlaydi (useLayoutEffect). Paint'dan keyingisi — useEffect da:

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

function App() {
  const [count, setCount] = useState(0);
  console.log("render:", count);
  useLayoutEffect(() => {
    const text = document.getElementById("son").textContent;
    console.log("commit tugadi, DOM'da:", text);
  });
  useEffect(() => {
    console.log("effekt:", count);
  });
  function handleClick() {
    console.log("trigger: setCount", count + 1);
    setCount(count + 1);
  }
  return (
    <div>
      <p id="son">{count}</p>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}

Qatorlar tartibiga qarang: har yangilanish uch qator beradi — render, commit, effekt. trigger qatori faqat bosishda chiqadi.

Konsolda:

text
render: 0
commit tugadi, DOM'da: 0
effekt: 0
trigger: setCount 1
render: 1
commit tugadi, DOM'da: 1
effekt: 1

3.2 Jadvalda

Qadam State DOM'da (#son) Ekranda
birinchi render 0 hali yo'q bo'sh
birinchi commit 0 0 paint'dan keyin 0
bosish → trigger hali 0 0 0
render 1 0 0
commit 1 1 paint'dan keyin 1

To'rtinchi qatorga e'tibor bering: setCount(1) chaqirildi, lekin count hali 0 — trigger: setCount 1 logi ham eski snapshot ichida (State II). Beshinchi qatorda App yangi qiymat bilan chaqirildi, lekin DOM hali eski: render DOM'ga tegmaydi. Faqat oltinchi qatorda — commit'da — <p> ichidagi matn 1 bo'ldi.

4. Qayta render ≠ DOM o'zgarishi

4.1 O'lchov: MutationObserver

Endi asosiy savol. App qayta chizilsa, React DOM'ni qayta yasaydimi? Taxmin qilmaymiz — o'lchaymiz. Brauzerda MutationObserver bor: u kuzatilayotgan DOM qismidagi har o'zgarishni (element qo'shildi, matn o'zgardi, atribut o'zgardi) yozib boradi (MutationObserver). takeRecords() — shu paytgacha yig'ilgan yozuvlarni darhol qaytaradi.

«Bahor» menyusidagi karta. Ikki tugma: «Yangila» — clicks ni oshiradi (u ekranda ko'rsatilmaydi), «Narx» — chegirmani yoqadi:

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

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

function App() {
  const [clicks, setClicks] = useState(0);
  const [cheap, setCheap] = useState(false);
  const box = useRef(null);
  console.log("render", clicks, cheap);
  useLayoutEffect(() => {
    observer.observe(box.current, {
      subtree: true,
      childList: true,
      characterData: true,
      attributes: true,
    });
    return () => observer.disconnect();
  }, []);
  useLayoutEffect(() => {
    const n = observer.takeRecords().length;
    console.log("→ DOM o'zgarishi:", n);
  });
  return (
    <div ref={box}>
      <h2>Osh</h2>
      <p>Narx: {cheap ? 30000 : 35000} so'm</p>
      <button onClick={() => setClicks(clicks + 1)}>Yangila</button>
      <button onClick={() => setCheap(!cheap)}>Narx</button>
    </div>
  );
}

Konsolda:

text
render 0 false
→ DOM o'zgarishi: 0
render 1 false
→ DOM o'zgarishi: 0
render 1 true
→ DOM o'zgarishi: 1

Ekranda:

text
Osh
Narx: 30000 so'm
YangilaNarx

Birinchi useLayoutEffect (bo'sh massiv bilan) kuzatishni bir marta boshlaydi. Ikkinchisi (massivsiz) har commit'dan keyin yig'ilgan o'zgarishlarni sanaydi. Effektlar yozilgan tartibda ishlaydi, shuning uchun birinchi chizishda hisob 0 — kuzatish endi boshlandi.

4.2 Natijani o'qiymiz

Bosish Render DOM o'zgarishi Nega
«Yangila» ha (render 1 false) 0 JSX natijasi o'sha — farq yo'q
«Narx» ha (render 1 true) 1 faqat bitta matn tuguni

«Yangila» App ni to'liq qayta chizdi: funksiya chaqirildi, JSX yangidan yasaldi, console.log ishladi. Lekin clicks JSX'da ishlatilmaydi — yangi tavsif eskisi bilan bir xil. React solishtirdi, farq topmadi va DOM'ga hech narsa yozmadi.

«Narx» da ham butun <p> yoki butun karta qayta yasalmadi — bitta o'zgarish. Narx: {narx} so'm JSX'da uchta alohida matn tuguniga aylanadi: "Narx: ", "35000", " so'm". React faqat o'rtadagisini almashtirdi (characterData yozuvi). <h2>, tugmalar, hatto <p> elementining o'zi — o'sha DOM obyektlari.

Maslahat: Bu natija ikki tomonlama. Yaxshi tomoni: keraksiz render DOM'ni buzmaydi — fokus, kursor, scroll joyida qoladi. Yomon tomoni: render baribir vaqt oladi. 10 000 qatorli ro'yxatda React 10 000 komponentni chaqirib, solishtirib, "farq yo'q" degan xulosaga keladi. Aynan shu "bekorga ish" keyingi darslarning mavzusi.

React ikki tavsifni qanday solishtiradi — Reconciliation, Virtual DOM va Fiber darsida.

Tekshirib ko'ring: «Yangila» ni 100 marta bossangiz, konsolda nechta render va DOM'da nechta o'zgarish bo'ladi?

Javob

100 ta render logi (har bosish — yangi clicks, demak trigger) va 0 ta DOM o'zgarishi. Har safar React funksiyani chaqiradi va natijani solishtiradi, lekin farq topmaydi. Agar App og'ir bo'lsa, 100 ta bekor render sezilarli vaqt oladi — DOM'ga tegilmasa ham.

5. Commit'siz render

5.1 StrictMode: ikki render, bitta commit

Render — faqat hisob bo'lgani uchun React uni xohlagancha qaytarishi mumkin. Dev rejimdagi StrictMode aynan shuni qiladi: har renderni ikki marta chaqiradi, toza bo'lmagan kodni ushlash uchun (StrictMode chuqur):

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

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

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

Konsolda:

text
render: 0
render: 0
effekt: 0
effekt: 0
render: 1
render: 1
effekt: 1

Bosishdan keyin: ikki render: 1, lekin bitta effekt: 1. Ikkala render bir xil natija berdi, commit esa bitta bo'ldi. Birinchi chizishdagi ikki effekt: 0 — boshqa narsa: StrictMode faqat birinchi chizishda effektni "o'rnat → tozala → qayta o'rnat" qilib sinaydi (Effekt hayot sikli). Brauzer konsolida React 19 ikkinchi render logini xiraroq rangda ko'rsatadi.

5.2 Transition: render tashlab yuboriladi

Ikkinchi holatni Concurrent rendering darsida ko'rgansiz: transition renderi bo'laklarda ketadi va muhimroq yangilanish kelsa — tashlab yuboriladi. Konsolda App: taomlar false bor edi, ekranga chiqdi: Taomlar esa yo'q edi.

Ikkala holatdan bitta qoida: render soni ≠ ekranga chiqqan holatlar soni. "Bu komponent ekranga chiqdi" ni bilmoqchi bo'lsangiz — logni effektga qo'ying (commit'dan keyin ishlaydi), render tanasiga emas.

6. Commit ichidagi tartib

6.1 Ref callback ham commit'da

Commit bitta harakat emas — ichida aniq tartib bor. Ref callback (useRef II) qayerga tushishini ko'ramiz:

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

function App() {
  const [count, setCount] = useState(0);
  console.log("render", count);
  useLayoutEffect(() => console.log("useLayoutEffect", count));
  useEffect(() => console.log("useEffect", count));
  return (
    <button
      ref={(el) => {
        console.log("ref", el.textContent);
        return () => console.log("ref tozalash");
      }}
      onClick={() => setCount(count + 1)}
    >
      +1
    </button>
  );
}

Konsolda:

text
render 0
ref +1
useLayoutEffect 0
useEffect 0
render 1
ref tozalash
ref +1
useLayoutEffect 1
useEffect 1

Ref callback DOM tayyor bo'lgach, useLayoutEffect dan oldin chaqirildi. Ikkinchi commit'da "ref tozalash" ham bor: ref callback JSX ichida yozilgan — har renderda yangi funksiya. React eski callback'ning tozalovchisini chaqiradi va yangisini ulaydi. Bu xato emas, lekin ref callback'da og'ir ish bo'lsa, uni komponentdan tashqariga yoki useCallback ga chiqaring (memo, useMemo, useCallback).

6.2 Tartib sxemasi

flowchart TD
  A["Render: komponentlar chaqiriladi"] --> B["Commit: DOM'ga farq yoziladi"]
  B --> C["Eski ref tozalanadi, yangisi ulanadi"]
  C --> D["useLayoutEffect"]
  D --> E["Brauzer: layout va paint"]
  E --> F["useEffect"]

useLayoutEffect va paint orasidagi chiziq muhim. useLayoutEffect da DOM'ni o'lchab, state o'zgartirsangiz — React yangi renderni paint'dan oldin bajaradi, foydalanuvchi oraliq holatni ko'rmaydi. Tooltip joylashuvi kabi holatlar uchun shu (useLayoutEffect). Lekin u paint'ni kechiktiradi — og'ir ish uchun emas.

Tekshirib ko'ring: Effekt ichida console.log(document.getElementById("son").textContent) — u eski yoki yangi qiymatni ko'rsatadimi?

Javob

Yangisini. useEffect ham, useLayoutEffect ham commit'dan keyin ishlaydi — DOM allaqachon yangilangan. Farqi faqat paint'ga nisbatan: useLayoutEffect — undan oldin, useEffect — odatda keyin. Eski qiymatni faqat render tanasida (DOM'ga qarasangiz) ko'rasiz — va aynan shuning uchun render ichida DOM o'qilmaydi.

7. Vazifalar qadami: render va commit'ni sanash (o'lchov, commit yo'q)

7.1 Qanday sanaldi

Kanonda #16–#18 — kod o'zgarishi emas, o'lchov (QADAMLAR.md). Savol: vazifalar da 1 000 vazifa bilan bitta harakat nechta commit va nechta komponent renderini beradi?

Har komponentga console.log qo'yish — 30 ta faylni o'zgartirish. Kanon boshqa yo'lni tanladi — React DevTools'ning usulini. DevTools brauzer sahifasiga __REACT_DEVTOOLS_GLOBAL_HOOK__ degan global obyekt qo'yadi. react-dom ishga tushganda shu obyektni topadi va har commit'dan keyin uning onCommitFiberRoot metodini chaqiradi. O'lchov skripti (olchov/renderlar.mjs) shu obyektning o'rinbosarini qo'yadi:

js
onCommitFiberRoot(_id, root) {
  // har commit — shu metod bir marta
  if (o.yoq) o.commitlar++;
  const stek = [root.current];
  while (stek.length) {
    const f = stek.pop();
    // komponent shu commit'da chizilganmi: PerformedWork (1)
    if (KOMPONENT.has(f.tag) && !oldingi.has(f) &&
        (f.alternate === null || (f.flags & 1) === 1)) {
      o.renderlar[nomi(f)] = (o.renderlar[nomi(f)] ?? 0) + 1;
    }
    if (f.sibling) stek.push(f.sibling);
    if (f.child) stek.push(f.child);
  }
  oldingi = hozirgi;
},

Kanondagi kod uzun qatorlarda — bu yerda qisqartirildi va telefon uchun qayta bo'lindi (hozirgi to'plamini yig'ish qatori tushirib qoldirildi). Hozircha root.current, child, sibling, flags so'zlarini tanimasangiz — tabiiy: bular Fiber daraxtining maydonlari, keyingi darsning mavzusi. G'oya oddiy: har commit'da daraxtni aylanib, "shu commit'da ishlagan" komponentlarni sanaymiz. Build minifikatsiyasiz yig'ildi — aks holda nomlar It, Nt bo'lardi.

7.2 Natija

Holat 12 (#02 dagi memo(VazifaRoyxati) bor), 1 000 vazifa, soxta server (natijalar/18-renderlar-12.txt):

Harakat Commit Komponent render
qidiruv: bitta harf ("v", hammasi mos) 2 6 035
katak: bitta vazifani belgilash 5 18 056
e'lon o'chishi (3 s taymer) 1 17
tema tanlovi (Context) 1 2

Uch narsani darhol ko'ramiz:

  1. Bitta bosish — 5 commit. Katak: optimistik yangilanish, server javobi, tasdiq (useOptimistic) — har biri o'z commit'i. Commit soni harakatlar soniga teng emas.
  2. Commit kam, render ko'p. 5 commit'da 18 056 render: commit'larning uchtasida 1 000 qatorning har biri (qator ichida 6 komponent) qayta chizildi. Hisob: 3 × 1 000 × 6 = 18 000, qolgan 56 tasi — ilovaning boshqa qismlari. Bitta katak o'zgardi, qolgan 999 tasi — o'sha. Lekin oldingi bo'limdagi «Yangila» tugmasidek: render bo'ldi, DOM deyarli o'zgarmadi.
  3. Tema — atigi 2. Context o'zgardi, lekin butun ilova qayta chizilmadi. Nega — Kompozitsiya orqali optimallashtirish darsida.

Vaqtda bu qancha? Event Timing bilan o'lchangan (natijalar/20-oldin.txt, Chrome headless): katak bosilgandan keyingi kadrgacha — 1 000 vazifada mediana 48 ms, 10 000 da 264 ms. Ikkinchisi Google mezonidagi 200 ms chegarasidan oshadi — "yaxshi emas" INP (Core Web Vitals). Bu 18 056 render qayerdan kelgani — Qayta render sabablari va miflar darsida, tuzatish esa — memo, useMemo, useCallback da (18 056 → 68).

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
"React DOM'ni qayta chizyapti, sekin" render va commit adashtirilgan DevTools Profiler yoki MutationObserver bilan o'lchang
log ikki marta chiqadi StrictMode dev'da render'ni ikki marta chaqiradi xato emas; "ekranga chiqdi" — effektda
render ichida DOM o'qilsa eski qiymat render paytida DOM hali yangilanmagan useLayoutEffect yoki ref + effekt
render ichida tashqi narsa o'zgardi, ekranga mos emas render tashlab yuborilgan yoki ikki marta yon ta'sir — effekt yoki hodisada
ref callback har renderda qayta ulanadi JSX ichida yangi funksiya tashqariga chiqaring yoki useCallback
"bitta bosish — bitta commit" deb hisoblash optimistik, server javobi — alohida commit'lar o'lchang, taxmin qilmang

9. Mashqlar

Mashqlar kurs/mashqlar/18/16-render-commit/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): bosqichlar tartibi

Tugma bosilganda nima qaysi tartibda bo'ladi? Raqam qo'ying:

  • useEffect ishlaydi:
  • setCount chaqiriladi (trigger):
  • DOM'dagi matn o'zgaradi (commit):
  • App() chaqiriladi (render):
  • useLayoutEffect ishlaydi:
Yechim

Trigger (1) → render (2) → commit (3) → useLayoutEffect (4) → paint → useEffect (5). Trigger faqat belgi qo'yadi, render faqat hisoblaydi, DOM esa commit'da o'zgaradi. Ikki effekt orasida brauzer bo'yaydi — useLayoutEffect paint'dan oldin, useEffect odatda keyin.

2-mashq (o'rta): kim DOM'ni o'zgartirdi?

«Bahor» buyurtma kartasida ikki tugma: «+1 porsiya» va «Eslatma» (eslatma vaqti state'ga yoziladi, lekin ekranda ko'rsatilmaydi). Kodni yozmasdan oldin bashorat qiling: har bosishdan keyin → DOM o'zgarishi: qancha bo'ladi? Keyin «Ishga tushir» bilan tekshiring.

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

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

function App() {
  const [portions, setPortions] = useState(1);
  const [remindAt, setRemindAt] = useState(null);
  const box = useRef(null);
  useLayoutEffect(() => {
    observer.observe(box.current, {
      subtree: true,
      childList: true,
      characterData: true,
    });
    return () => observer.disconnect();
  }, []);
  useLayoutEffect(() => {
    console.log("→ DOM o'zgarishi:", observer.takeRecords().length);
  });
  return (
    <div ref={box}>
      <p>Osh × {portions}</p>
      <button onClick={() => setPortions(portions + 1)}>
        +1 porsiya
      </button>
      <button onClick={() => setRemindAt("12:00")}>Eslatma</button>
    </div>
  );
}
Yechim

Konsolda:

text
→ DOM o'zgarishi: 0
→ DOM o'zgarishi: 0
→ DOM o'zgarishi: 1

Birinchi qator — birinchi chizish (kuzatish endi boshlandi). «Eslatma» — remindAt o'zgardi, render bo'ldi, lekin JSX'da ishlatilmaydi: 0. «+1 porsiya» — {portions} matn tuguni 1 dan 2 ga: 1 ta o'zgarish. "Osh × " matni va <p> ning o'zi tegilmadi. Ekranda remindAt kerak bo'lmasa — u state bo'lishi shartmi? Agar faqat hodisada ishlatilsa, useRef yetadi va render ham bo'lmaydi (useRef I).

3-mashq (qiyin): commit hisoblagichi

OrderBadge komponenti nechta marta ekranga chiqqanini (commit) sanasin va konsolga commit №1, commit №2 … deb yozsin. Shart: StrictMode ostida ham hisob to'g'ri bo'lsin — ikki marta chaqirilgan render hisobni ikki barobar oshirmasin. Ishora: hisobni useRef da saqlang (useRef I); qaysi bosqichda oshirish kerak — «Commit'siz render» bo'limidan.

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

function OrderBadge({ count }) {
  // shu yerda: commit'larni sanang
  return <p>Buyurtmalar: {count}</p>;
}

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <OrderBadge count={count} />
      <button onClick={() => setCount(count + 1)}>
        Yangi buyurtma
      </button>
    </div>
  );
}

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>,
);
Yechim
jsx
import { StrictMode, useEffect, useRef, useState } from "react";
import { createRoot } from "react-dom/client";

function OrderBadge({ count }) {
  const commits = useRef(0);
  useEffect(() => {
    commits.current += 1; // commit'dan keyin — faqat bir marta
    console.log("commit №" + commits.current, "count:", count);
  });
  return <p>Buyurtmalar: {count}</p>;
}

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <OrderBadge count={count} />
      <button onClick={() => setCount(count + 1)}>
        Yangi buyurtma
      </button>
    </div>
  );
}

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

Konsolda:

text
commit №1 count: 0
commit №2 count: 0
commit №3 count: 1
commit №4 count: 2

Ikki bosish — ikki yangilanish (count: 1, count: 2) va har biriga bittadan commit: renderning ikki marta chaqirilishi hisobga ta'sir qilmadi. Lekin boshida №1 va №2 — ikkalasi count: 0. Bu StrictMode'ning effekt sinovi: birinchi chizishda effekt ikki marta ishlaydi (tozalovchi yo'q, shuning uchun hisob ikki marta oshdi). Production'da bu bo'lmaydi — u yerda №1, №2, №3. Hisobni render tanasida (commits.current += 1 funksiya boshida) oshirsangiz — StrictMode'da har yangilanishda +2 bo'lardi, transition'da esa tashlangan renderlar ham sanalardi.

4-mashq: vazifalar da "Highlight updates"

vazifalar ning holat 12 dagi build'ini 1 000 vazifa bilan oching (localStorage ga yozish — useTransition darsidagi 4-mashq kabi). React DevTools sozlamalarida "Highlight updates when components render" ni yoqing (React DevTools). Bitta katakni bosing. Nimani ko'rasiz va bu «Vazifalar qadami» dagi qaysi raqamga mos?

Yechim

Butun ro'yxat bir necha marta chaqnaydi — har qator ramka bilan. Bu jadvaldagi 18 056 render: 5 commit'ning uchtasida 1 000 qatorning hammasi qayta chizildi. DOM'da esa faqat bitta qator o'zgardi (katak va bajarildi klassi) — "Highlight updates" render'ni ko'rsatadi, DOM o'zgarishini emas. Sababi va tuzatishi keyingi uch darsda.

10. Real ishda

  • Performans muhokamasi: jamoada "sekin" degan gapga birinchi savol — "render sekinmi, commit sekinmi yoki paint?". Har biriga boshqa davo: render — kamroq komponent chizish, commit — kamroq DOM o'zgarishi, paint — CSS va joylashuv.
  • Analitika va loglar: "sahifa ko'rildi" hodisasi effektda yuboriladi, render tanasida emas — aks holda StrictMode va transition'da ikki marta sanaladi.
  • Kutubxonalar: animatsiya (Framer Motion), o'lchov (react-use-measure) kutubxonalari useLayoutEffect ga tayanadi — commit va paint orasidagi joy aynan ular uchun.
  • Intervyu savollari: "Render va commit farqi?", "Qayta render bo'lsa, DOM ham o'zgaradimi?", "Nega render toza bo'lishi kerak?", "useLayoutEffect qachon ishlaydi?".

Xulosa

  • To'rt bosqich: trigger (setState, birinchi render) → render (komponentlar chaqiriladi, JSX tavsif) → commit (DOM'ga faqat farq) → paint (brauzer).
  • Render — hisob, DOM'ga tegmaydi; u tashlab yuborilishi yoki StrictMode'da ikki marta chaqirilishi mumkin.
  • Qayta render ≠ DOM o'zgarishi: natija o'sha bo'lsa — 0 o'zgarish; o'zgarsa — faqat farq (bitta matn tuguni).
  • Commit ichida: DOM → ref → useLayoutEffect → paint → useEffect. "Ekranga chiqdi" ni effektda sanang.
  • vazifalar, 1 000 vazifa: bitta katak — 5 commit, 18 056 komponent render; tema — 2 render.

Keyingi dars: Reconciliation, Virtual DOM va Fiber — React ikki tavsifni qanday solishtiradi: element turi, key va Fiber daraxti.

Manbalar

  • React: "Render and Commit" — react.dev/learn/render-and-commit
  • React: useLayoutEffect — react.dev/reference/react/useLayoutEffect
  • MDN: MutationObserver.takeRecords() — developer.mozilla.org/docs/Web/API/MutationObserver/takeRecords
  • Kanon: sinov/18/QADAMLAR.md (#16–#18), olchov/renderlar.mjs, natijalar/18-renderlar-12.txt, natijalar/20-oldin.txt
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Render va commit: React ekranni qanday yangilaydi — IlmHamroh