Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. To'rt bosqich
- 2.1 Umumiy rasm
- 2.2 Trigger — render nimadan boshlanadi
- 2.3 Render — hisob-kitob
- 2.4 Commit va paint
- 3. Bosqichlar konsolda
- 3.1 Uch log
- 3.2 Jadvalda
- 4. Qayta render ≠ DOM o'zgarishi
- 4.1 O'lchov: MutationObserver
- 4.2 Natijani o'qiymiz
- 5. Commit'siz render
- 5.1 StrictMode: ikki render, bitta commit
- 5.2 Transition: render tashlab yuboriladi
- 6. Commit ichidagi tartib
- 6.1 Ref callback ham commit'da
- 6.2 Tartib sxemasi
- 7. Vazifalar qadami: render va commit'ni sanash (o'lchov, commit yo'q)
- 7.1 Qanday sanaldi
- 7.2 Natija
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bosqichlar tartibi
- 2-mashq (o'rta): kim DOM'ni o'zgartirdi?
- 3-mashq (qiyin): commit hisoblagichi
- 4-mashq: vazifalar da "Highlight updates"
- 10. Real ishda
- Xulosa
- Manbalar
Render va commit: React ekranni qanday yangilaydi
Qisqacha: React ekranni to'rt bosqichda yangilaydi: trigger (
setStateyoki birinchiroot.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. BuniMutationObserverbilan 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. vazifalarda "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: paintDiagrammadagi 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:
- Birinchi chizish —
createRoot(el).render(<App />)(Birinchi React loyiha). React butun daraxtni noldan chizadi. - Holat o'zgarishi —
setState,dispatch, context qiymati o'zgarishi,useSyncExternalStoredagi 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:
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:
render: 0
commit tugadi, DOM'da: 0
effekt: 0
trigger: setCount 1
render: 1
commit tugadi, DOM'da: 1
effekt: 13.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:
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:
render 0 false
→ DOM o'zgarishi: 0
render 1 false
→ DOM o'zgarishi: 0
render 1 true
→ DOM o'zgarishi: 1Ekranda:
Osh
Narx: 30000 so'm
YangilaNarxBirinchi 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
renderva 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):
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:
render: 0
render: 0
effekt: 0
effekt: 0
render: 1
render: 1
effekt: 1Bosishdan 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:
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:
render 0
ref +1
useLayoutEffect 0
useEffect 0
render 1
ref tozalash
ref +1
useLayoutEffect 1
useEffect 1Ref 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:
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:
- Bitta bosish — 5 commit. Katak: optimistik yangilanish, server javobi, tasdiq (useOptimistic) — har biri o'z commit'i. Commit soni harakatlar soniga teng emas.
- 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.
- 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:
useEffectishlaydi:setCountchaqiriladi (trigger):- DOM'dagi matn o'zgaradi (commit):
App()chaqiriladi (render):useLayoutEffectishlaydi:
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.
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:
→ DOM o'zgarishi: 0
→ DOM o'zgarishi: 0
→ DOM o'zgarishi: 1Birinchi 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.
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
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:
commit №1 count: 0
commit №2 count: 0
commit №3 count: 1
commit №4 count: 2Ikki 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
useLayoutEffectga 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?", "
useLayoutEffectqachon ishlaydi?".
Xulosa
- To'rt bosqich: trigger (
setState, birinchirender) → 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
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!