IlmHamroh
JavaScript Full-stack/17-qism. React asoslari36/45-dars17 daqiqa
Mundarija (27)

React useLayoutEffect: ekran chizilishidan oldin o'lchash va miltillashning oldini olish

Qisqacha: useEffect brauzer ekranni chizgandan keyin ishlashi mumkin, useLayoutEffect esa DOM tayyor bo'lgach, lekin chizishdan oldin ishlaydi. Tooltip, popover yoki chat oxiri kabi joylarda element o'lchanadi va joyi o'zgaradi. Buni useEffect da qilsangiz, foydalanuvchi bir-ikki kadr noto'g'ri holatni ko'radi — miltillash. useLayoutEffect buni yo'qotadi, lekin chizishni to'xtatib turadi, shuning uchun uni faqat o'lchash uchun ishlating. Qolgan hamma joyda — useEffect.

Bu darsda

  • React'da render, commit, chizish (paint) va effektlar qaysi tartibda ishlashini ayta olasiz.
  • Miltillash qayerdan kelishini va uni useLayoutEffect bilan qanday yo'qotishni tushunasiz.
  • Tooltip'ni o'lchab, to'g'ri joyga qo'yadigan komponent yozasiz.
  • useLayoutEffect narxini va serverda (SSR) nima bo'lishini bilasiz.
  • useInsertionEffect kim uchun ekanini ajratasiz.

Oldin bilishingiz kerak: useEffect I: tashqi tizim bilan sinxronlash, Effekt hayot sikli va StrictMode, useRef II: DOM elementlari, Element o'lchamlari va scroll.

1. Nega bu kerak?

«Bahor» menyusida taom nomi ustiga sichqonchani olib borsangiz, kichik yozuv chiqadi: "Achchiq taom". Bunday yozuv tooltip ("ishora oynasi") deb ataladi. Tooltip tugmaning tepasida chiqishi kerak: uning balandligini bilmasdan, qancha yuqoriga ko'tarishni hisoblab bo'lmaydi.

Sardor tooltip'ni useEffect bilan yozdi: effekt tooltip balandligini o'lchaydi va joyini state'ga yozadi. Kompyuterida hammasi joyida edi. Jasur aka eski telefonida ochdi: "Yozuv avval pastda chiqib, keyin tepaga sakrayapti". Bu — miltillash (flicker): foydalanuvchi bir lahzaga oraliq, noto'g'ri holatni ko'radi.

Hayotiy o'xshatish: fotosuratchi. U suratga olishdan oldin mehmonlarni joy-joyiga qo'yadi — "siz biroz chaproqqa". Agar avval suratga olib, keyin joylashtirsa, birinchi surat qiyshiq chiqadi. useLayoutEffect — "suratdan oldin joylashtirish", useEffect — "suratdan keyin".

2. Vanilla JavaScript'da bu muammo yo'q edi

2.1 Bitta vazifa — bitta chizish

Vanilla'da tooltip shunday yozilardi:

js
// sichqoncha kirganda — hammasi bitta ishlovchida
button.addEventListener("pointerenter", () => {
  tip.hidden = false;                         // 1) DOM'ga qo'shdik
  const { height } = tip.getBoundingClientRect(); // 2) o'lchadik
  tip.style.top = `${-height - 4}px`;         // 3) joyladik
});

Uch qadam ham bitta vazifada (task) bajariladi. Brauzer ekranni JavaScript vazifasi tugagandan keyin chizadi (Rendering pipeline). getBoundingClientRect() esa joylashuvni o'sha zahoti hisoblatadi (Element o'lchamlari). Natijada foydalanuvchi faqat oxirgi holatni ko'radi — tooltip darhol tepada.

2.2 React'da qadamlar bo'linadi

React'da esa bu qadamlar alohida bosqichlarga taqsimlangan. Komponent avval chiziladi (render), React DOM'ni yangilaydi (commit), keyin effektlar ishlaydi. Savol: effekt brauzer ekranni chizishidan oldin ishlaydimi yoki keyin?

useEffect uchun javob: odatda keyin. React effektlarni ekran chizilishini kechiktirmaslik uchun keyinga qoldiradi. Ko'p ishlar uchun bu juda yaxshi: server so'rovi, obuna, localStorage (useEffect I) foydalanuvchini kutdirmaydi. Lekin o'lchash uchun — kech: foydalanuvchi birinchi kadrda o'lchanmagan holatni ko'rib ulguradi.

3. Tartib: render → commit → layout effekt → chizish → effekt

3.1 Diagramma

flowchart TD
  R["1. Render: komponent JSX qaytaradi"] --> C["2. Commit: React DOM'ni yangilaydi"]
  C --> L["3. useLayoutEffect: DOM bor, ekran hali eski"]
  L --> P["4. Brauzer ekranni chizadi (paint)"]
  P --> E["5. useEffect"]
  L -. "setState bo'lsa — qayta render, chizishdan oldin" .-> R

Diagrammadagi asosiy narsa — 3- va 4-qadamlar orasidagi chegara. useLayoutEffect DOM tayyor bo'lganda, lekin foydalanuvchi uni hali ko'rmasdan ishlaydi. Shu yerda o'lchab, state o'zgartirsangiz, React qayta chizadi — va brauzer faqat yakuniy natijani ko'rsatadi (punktir o'q).

3.2 Konsolda tekshiramiz

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

function App() {
  const [count, setCount] = useState(0);
  const button = useRef(null);
  console.log("render", count);

  useLayoutEffect(() => {
    console.log("layout", count, button.current.textContent);
    return () => console.log("layout tozalash", count);
  });
  useEffect(() => {
    console.log("effekt", count);
    return () => console.log("effekt tozalash", count);
  });

  return (
    <button ref={button} onClick={() => setCount(count + 1)}>
      +1
    </button>
  );
}

Konsolda:

text
render 0
layout 0 +1
effekt 0
render 1
layout tozalash 0
layout 1 +1
effekt tozalash 0
effekt 1

Ekranda:

text
+1

Ikki narsaga qarang:

  • layout 0 +1 — layout effekt ichida button.current allaqachon tayyor, matnini o'qiy olamiz. DOM bor, ref qo'yilgan.
  • Yangilanishda tartib: avval layout effektning tozalashi va yangisi, keyin oddiy effektning tozalashi va yangisi. Cleanup — tozalash funksiyasi (useEffect III) — har doim o'z turidagi yangi effektdan oldin keladi.
Qadam count Nima ishladi Ekranda
birinchi render 0 render, layout, (chizish), effekt +1
bosildi → render 1 render hali eski kadr
commit 1 layout tozalash → layout hali eski kadr
chizish 1 brauzer yangi kadr
keyin 1 effekt tozalash → effekt yangi kadr

Tekshirib ko'ring: Ikkala effektda ham document.title = "Savat: " + count yozish mumkin. Qaysi biri to'g'ri tanlov?

Javob

useEffect. Sahifa sarlavhasi — brauzer yorlig'idagi matn, uni o'lchash kerak emas va bir kadr kechikishi hech kimga ko'rinmaydi. useLayoutEffect faqat DOM'ni o'lchab, shu o'lchovga qarab ko'rinishni o'zgartirish kerak bo'lganda kerak.

4. Miltillashni o'lchab ko'ramiz

4.1 Tajriba

Gapga ishonmasdan, o'lchadik. Tooltip komponenti ikki variantda: effekt useEffect da va useLayoutEffect da. Sahifa haqiqiy Chrome 154.0.8037.98 da (headless) ochildi. Puppeteer sichqonchani tugma ustiga olib bordi va tooltip paydo bo'ldi. Har kadrda (requestAnimationFrame chaqiruvida) tooltip qayerda turganini yozib bordik: "pastda" (o'lchanmagan) yoki "tepada" (to'g'ri). Har variant — 10 urinish.

Ikkinchi qatorda komponent renderiga ataylab 50 ms "og'irlik" qo'shdik. Sekin telefonda yoki katta komponentda render shunday cho'ziladi.

Tooltip noto'g'ri joyda turgan kadrlar (10 urinish, mediana)
  • useEffect10 dan 8 tasida 1 kadr, 2 tasida 2 kadr1 kadr
  • useLayoutEffect10 dan 10 tasida 00 kadr
  • useEffect + 50 ms render10 dan 10 tasida 2 kadr2 kadr
  • useLayoutEffect + 50 ms render10 dan 10 tasida 00 kadr

Manba: O'lchov 2026-10-13: Chrome 154.0.8037.98 headless, React 19.3.0, puppeteer-core hover, har kadr requestAnimationFrame'da yozildi

Grafikning ma'nosi:

  • useEffect bilan foydalanuvchi har safar kamida bitta kadr tooltip'ni noto'g'ri joyda ko'rdi. 60 Hz ekranda bitta kadr — taxminan 16.7 ms. Kompyuterda bu deyarli sezilmaydi, sekin telefonda esa "sakrash" bo'lib ko'rinadi.
  • Render og'irlashsa, xato kadrlar soni ko'paydi (2 ta).
  • useLayoutEffect bilan — 40 urinishning birortasida ham noto'g'ri kadr bo'lmadi.

4.2 Miltillaydigan variant

Quyidagi kodda tooltip renderiga 200 ms og'irlik qo'shilgan — farq ko'z bilan ko'rinsin. «Ishga tushir» ni bosing va «Lag'mon» ni bir necha marta bosib ko'ring: yozuv avval pastda chiqib, keyin tepaga sakraydi.

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

function slowDown(ms) {
  const start = performance.now();
  while (performance.now() - start < ms) {} // ataylab sekin render
}

function Tooltip({ text }) {
  const tip = useRef(null);
  const [top, setTop] = useState(null); // null — hali o'lchanmagan
  slowDown(200);

  useEffect(() => { // ❌ chizishdan keyin
    const { height } = tip.current.getBoundingClientRect();
    setTop(-height - 4);
  }, []);

  const style = {
    position: "absolute", left: 0, top: top ?? 28,
    background: "#222", color: "#fff", padding: "2px 8px",
  };
  return <span ref={tip} role="tooltip" style={style}>{text}</span>;
}

function App() {
  const [shown, setShown] = useState(false);
  return (
    <p style={{ position: "relative", marginTop: 40 }}>
      <button onClick={() => setShown(!shown)}>Lag'mon</button>{" "}
      {shown && <Tooltip text="Achchiq taom" />}
    </p>
  );
}

Ekranda:

text
Lag'mon Achchiq taom

top ?? 28 — o'lchanmagan paytda tooltip tugma ostida (28 px pastda). Effekt o'lchab, tepaga ko'chiradi. Ekran matni ikkala holatda bir xil — farq faqat joylashuvda, uni ko'z bilan ko'rasiz.

4.3 Tuzatilgan variant

Bitta so'z o'zgaradi: useEffect → useLayoutEffect.

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

function slowDown(ms) {
  const start = performance.now();
  while (performance.now() - start < ms) {}
}

function Tooltip({ text }) {
  const tip = useRef(null);
  const [top, setTop] = useState(null);
  slowDown(200);

  useLayoutEffect(() => { // ✅ chizishdan oldin
    const { height } = tip.current.getBoundingClientRect();
    setTop(-height - 4);
  }, []);

  const style = {
    position: "absolute", left: 0, top: top ?? 28,
    background: "#222", color: "#fff", padding: "2px 8px",
  };
  return <span ref={tip} role="tooltip" style={style}>{text}</span>;
}

function App() {
  const [shown, setShown] = useState(false);
  return (
    <p style={{ position: "relative", marginTop: 40 }}>
      <button onClick={() => setShown(!shown)}>Lag'mon</button>{" "}
      {shown && <Tooltip text="Achchiq taom" />}
    </p>
  );
}

Ekranda:

text
Lag'mon Achchiq taom

Endi tooltip kechikib chiqadi (render sekin — 200 ms ikki marta), lekin darhol to'g'ri joyda. Foydalanuvchi uchun "biroz kech, lekin to'g'ri" — "tez, lekin sakraydigan"dan yaxshiroq. Haqiqiy loyihada slowDown bo'lmaydi va kechikish sezilmaydi.

5. Layout effekt ichida state o'zgartirish

5.1 Qayta render — chizishdan oldin

Tooltip misolida setTop layout effekt ichida chaqirildi. React bunday yangilanishni darhol, sinxron qayta ishlaydi. Tartibni o'lchov o'rniga oddiy son bilan ko'ramiz:

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

function App() {
  const [width, setWidth] = useState(null);
  console.log("render", width);

  useLayoutEffect(() => {
    console.log("layout");
    if (width === null) setWidth(120); // "o'lchadik"
  }, [width]);

  useEffect(() => {
    console.log("effekt", width);
  }, [width]);

  return <p>{width === null ? "o'lchanmoqda" : `eni: ${width}`}</p>;
}

Konsolda:

text
render null
layout
effekt null
render 120
layout
effekt 120

Ekranda:

text
eni: 120
Qadam width Nima bo'ldi Foydalanuvchi ko'radimi?
1-render null DOM'ga "o'lchanmoqda" yozildi yo'q
layout null → 120 setWidth — sinxron yangilanish yo'q
effekt null birinchi commit'ning effekti yo'q
2-render, layout, effekt 120 DOM'ga "eni: 120" ha — birinchi chizilgan kadr

Qiziq joy — effekt null. React navbatdagi sinxron renderdan oldin birinchi commit'ning kutib turgan effektlarini ham bajarib oladi. Lekin bularning hammasi chizishdan oldin sodir bo'ldi: foydalanuvchi "o'lchanmoqda" so'zini hech qachon ko'rmaydi. Buni react.dev kafolat sifatida yozadi: useLayoutEffect ichidagi kod va undagi state yangilanishlari brauzer ekranni qayta chizishidan oldin qayta ishlanadi.

5.2 Narxi: chizish kutib turadi

Bu kafolatning narxi bor. Layout effekt va undan keyingi qayta render tugamaguncha brauzer hech narsa chizmaydi. «Tuzatilgan variant» dagi misolda tooltip 400 ms kechikdi — shu vaqt ichida sahifa "qotib" turdi. Og'ir ishni (katta hisob, so'rov) layout effektga qo'ysangiz, butun ilova sekinlashadi va INP ko'rsatkichi yomonlashadi (Performance API va Core Web Vitals).

Ish Qaysi hook Nega
server so'rovi, obuna, localStorage useEffect ekranga ta'siri yo'q, kutdirmasin
document.title useEffect bir kadr kechikish sezilmaydi
tooltip, popover, menyu joylashuvi useLayoutEffect o'lcham → ko'rinish
chat oxirigacha aylantirish useLayoutEffect aks holda avval yuqorida ko'rinadi
matn maydoni balandligini kontentga moslash useLayoutEffect balandlik o'lchovga bog'liq
fokus qo'yish useEffect yetadi joylashuv o'zgarmaydi

Qoida bitta: avval useEffect yozing. Miltillashni ko'rsangiz (yoki o'lchov → ko'rinish zanjiri bo'lsa) — useLayoutEffect ga almashtiring.

Maslahat: react.dev useEffect haqida yana bir nozik joyni aytadi: effekt bosish kabi foydalanuvchi harakatidan kelib chiqsa, React uni ba'zan chizishdan oldin ham bajaradi. Bunga tayanmang — "ba'zan" kafolat emas. O'lchash kerak bo'lsa, useLayoutEffect ning o'zini yozing.

Tekshirib ko'ring: Ro'yxatda 5 000 qator bor. Sardor har qatorning balandligini o'lchab, jamini state'ga yozish uchun useLayoutEffect ishlatdi. Sahifa ochilishi 1 soniyaga cho'zildi. Nima maslahat berasiz?

Javob

Layout effekt chizishni to'sib turadi — 5 000 ta o'lchov tugamaguncha foydalanuvchi bo'sh ekranni ko'radi. Yechimlar: faqat ko'rinib turgan qatorlarni chizish va o'lchash (virtualizatsiya, Uzun ro'yxatlar), yoki o'lchov natijasi birinchi kadrda shart bo'lmasa — useEffect.

6. Serverda (SSR) useLayoutEffect

6.1 Server o'lchay olmaydi

Ba'zi saytlarda HTML serverda tayyorlanadi — SSR (server-side rendering, "serverda chizish"). Next.js kabi framework'lar shunday ishlaydi — ularni keyingi qismlarda o'rganamiz. Serverda brauzer ham, ekran ham yo'q: getBoundingClientRect ni chaqirib bo'lmaydi. Shuning uchun React serverda hech qanday effektni ishga tushirmaydi — na useEffect, na useLayoutEffect.

React 19.3.0 da tekshirdik (react-dom/server, renderToString): useLayoutEffect li komponent jim chiziladi, ogohlantirish yo'q. React 18 esa shunday holatda useLayoutEffect does nothing on the server… deb ogohlantirardi — eski maqolalar va kutubxonalarda shu ogohlantirishni chetlab o'tish usullarini uchratasiz.

6.2 "Izomorf" layout effekt

Kutubxonalarda shunday qatorni ko'p ko'rasiz. Masalan, tooltip va popover joylashuvini hisoblovchi Floating UI (@floating-ui/react-dom 2.1.9, npm paket manbasidan):

js
var isClient = typeof document !== 'undefined';
var noop = function noop() {};
var index = isClient ? useLayoutEffect : noop;

Brauzerda — useLayoutEffect, serverda — hech narsa. Boshqa kutubxonalarda serverda useEffect qo'yiladi va bunday hook useIsomorphicLayoutEffect deb ataladi. Muhimi tushuncha: serverdan kelgan HTML o'lchanmagan holatda bo'ladi. Brauzer uni ko'rsatadi, keyin React uni jonlantiradi (hydration — HTML ga hodisa ishlovchilarini ulash) va layout effekt ishlaydi. Demak SSR'da birinchi kadrdagi "o'lchanmagan" ko'rinishni o'zingiz o'ylab qo'yishingiz kerak: masalan, tooltip'ni boshida yashirin chizish. Bizning vazifalar da SSR yo'q — bu masala keyingi qismlarda.

7. useInsertionEffect — kutubxona mualliflari uchun

Uchinchi effekt hook ham bor. U layout effektlardan ham oldin ishlaydi:

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

function App() {
  const [count, setCount] = useState(0);
  const button = useRef(null);
  useInsertionEffect(() => {
    console.log("insertion", count, "ref:", button.current !== null);
  });
  useLayoutEffect(() => {
    console.log("layout", count, "ref:", button.current !== null);
  });
  useEffect(() => console.log("effekt", count));
  return (
    <button ref={button} onClick={() => setCount(count + 1)}>
      +1
    </button>
  );
}

Konsolda:

text
insertion 0 ref: false
layout 0 ref: true
effekt 0
insertion 1 ref: true
layout 1 ref: true
effekt 1

Ekranda:

text
+1

Birinchi qatorda ref: false — insertion effekt paytida React ref'larni hali ulamagan. Bu hook CSS-in-JS kutubxonalari (styled-components, Emotion — CSS'ni JavaScript ichida yozish usuli, React'da stil berish) uchun yaratilgan. Ular <style> teglarini layout effektlar DOM'ni o'lchashidan oldin qo'shib ulgurishi kerak. Aks holda o'lchov stilsiz elementdan olinardi.

Ilova kodida useInsertionEffect deyarli kerak bo'lmaydi. Unda state o'zgartirish ham mumkin emas:

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

function App() {
  const [count, setCount] = useState(0);
  useInsertionEffect(() => {
    setCount(1); // ❌
  }, []);
  return <p>{count}</p>;
}

Konsolda:

text
useInsertionEffect must not schedule updates.

Ekranda:

text
1

"useInsertionEffect yangilanishlarni rejalashtirmasligi kerak." Bu safar ekran o'zgardi, lekin React ogohlantirdi: bunday kodning xulqi kafolatlanmagan.

Hook Qachon ishlaydi Ref tayyormi Kim uchun
useInsertionEffect layout effektlardan oldin yo'q (birinchi marta) CSS-in-JS kutubxonalari
useLayoutEffect DOM tayyor, chizishdan oldin ha o'lchash → joylashuv
useEffect odatda chizishdan keyin ha qolgan hamma narsa

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
tooltip, menyu bir zum boshqa joyda chiqadi o'lchov useEffect da useLayoutEffect
sahifa ochilishi yoki bosish sekinlashdi og'ir ish layout effektda useEffect ga ko'chiring yoki ishni kamaytiring
ref.current — null useInsertionEffect da DOM o'qilyapti useLayoutEffect
useInsertionEffect must not schedule updates. insertion effektda setState useLayoutEffect
serverdan kelgan sahifada element sakraydi SSR'da effektlar ishlamaydi boshlang'ich ko'rinishni o'ylang (yashirin chizish)
cheksiz qayta render layout effektda shartsiz setState shart qo'ying: if (width === null)

Oxirgi qator haqida: layout effektdagi setState ham oddiy effektdagidek qayta render beradi. Bog'liqliklar massivisiz va shartsiz yozsangiz — cheksiz sikl (useEffect II dagi tuzoq).

9. Mashqlar

Mashqlar kurs/mashqlar/17/36-layout-effekt/ da.

1-mashq (oson): tartibni toping

Kodni ishga tushirmasdan, «Keyingi» bir marta bosilgandan keyin konsolda qanday qatorlar chiqishini yozing.

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

function App() {
  const [step, setStep] = useState(1);
  useEffect(() => {
    console.log("E", step);
    return () => console.log("E-tozalash", step);
  });
  useLayoutEffect(() => {
    console.log("L", step);
    return () => console.log("L-tozalash", step);
  });
  return <button onClick={() => setStep(step + 1)}>Keyingi</button>;
}

Bosilgandan keyingi birinchi qator: [:L-tozalash 1]. Oxirgi qator: [:E 2].

Yechim

Konsolda:

text
L 1
E 1
L-tozalash 1
L 2
E-tozalash 1
E 2

Kodda useEffect yuqorida yozilgan bo'lsa ham, layout effektlar har doim oldin. Bitta turdagi effektlar esa kodda yozilgan tartibda ishlaydi.

2-mashq (o'rta): chat oxirigacha

«Bahor» qo'llab-quvvatlash chatida yangi xabar kelganda ro'yxat oxirigacha aylantiriladi. Sardor useEffect ishlatgan — yangi xabar kelganda ro'yxat bir zum yuqorida ko'rinib, keyin pastga "sakraydi". Tuzating va qo'shimcha talabni bajaring: aylantirish faqat xabarlar soni o'zgarganda bo'lsin (bog'liqlik — messages.length). Element oxiriga aylantirish — el.scrollTop = el.scrollHeight (Element o'lchamlari va scroll).

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

function ChatLog({ messages }) {
  const box = useRef(null);
  useEffect(() => {
    box.current.scrollTop = box.current.scrollHeight;
  });
  return (
    <ul ref={box} style={{ height: 80, overflowY: "auto" }}>
      {messages.map((m, i) => <li key={i}>{m}</li>)}
    </ul>
  );
}

function App() {
  const [messages, setMessages] = useState(["Salom!", "Stol bormi?"]);
  const add = () => setMessages([...messages, "Ha, 19:00 da"]);
  return (
    <div>
      <ChatLog messages={messages} />
      <button onClick={add}>Xabar keldi</button>
    </div>
  );
}

Ekranda:

text
Salom!
Stol bormi?
Ha, 19:00 da
Xabar keldi
Yechim
jsx
import { useLayoutEffect, useRef, useState } from "react";

function ChatLog({ messages }) {
  const box = useRef(null);
  useLayoutEffect(() => {
    box.current.scrollTop = box.current.scrollHeight;
  }, [messages.length]);
  return (
    <ul ref={box} style={{ height: 80, overflowY: "auto" }}>
      {messages.map((m, i) => <li key={i}>{m}</li>)}
    </ul>
  );
}

function App() {
  const [messages, setMessages] = useState(["Salom!", "Stol bormi?"]);
  const add = () => setMessages([...messages, "Ha, 19:00 da"]);
  return (
    <div>
      <ChatLog messages={messages} />
      <button onClick={add}>Xabar keldi</button>
    </div>
  );
}

Ekranda:

text
Salom!
Stol bormi?
Ha, 19:00 da
Xabar keldi

Aylantirish — o'lchovga (scrollHeight) bog'liq ko'rinish o'zgarishi, shuning uchun layout effekt. Bog'liqlik [messages.length]: xabar matni tahrirlansa yoki boshqa sabab bilan qayta render bo'lsa, foydalanuvchi o'qiyotgan joyidan tortib ketilmaydi. Indeks key sifatida bu yerda xavfsiz: xabarlar faqat oxiriga qo'shiladi (Ro'yxatlar va key).

3-mashq (qiyin): joy topadigan tooltip

Tooltip tepada sig'masa, pastga tushsin. Avval sof funksiya yozing (React'siz sinash oson):

tooltipPlacement(anchorTop, tipHeight, gap) — anchorTop tugmaning ekran tepasidan masofasi. Tepada joy yetsa (anchorTop - tipHeight - gap >= 0), "tepada", aks holda "pastda".

Keyin Tooltip komponenti: tugma va tooltip o'lchamlarini useLayoutEffect da getBoundingClientRect() bilan oladi, natijani state'ga yozadi va data-placement atributiga chiqaradi. Birinchi renderda (o'lchanmagan) tooltip visibility: "hidden" bo'lsin — shunda SSR'da ham noto'g'ri joy ko'rinmaydi.

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

export function tooltipPlacement(anchorTop, tipHeight, gap) {
  return anchorTop - tipHeight - gap >= 0 ? "tepada" : "pastda";
}

function Tooltip({ anchor, text }) {
  const tip = useRef(null);
  const [placement, setPlacement] = useState(null);
  useLayoutEffect(() => {
    const a = anchor.current.getBoundingClientRect();
    const t = tip.current.getBoundingClientRect();
    setPlacement(tooltipPlacement(a.top, t.height, 4));
  }, [anchor]);
  const style = {
    position: "absolute", left: 0,
    visibility: placement === null ? "hidden" : "visible",
    ...(placement === "pastda"
      ? { top: "100%" }
      : { bottom: "100%" }),
  };
  return (
    <span ref={tip} role="tooltip" data-placement={placement ?? ""}
      style={style}>
      {text}
    </span>
  );
}

function App() {
  const button = useRef(null);
  const [shown, setShown] = useState(false);
  return (
    <p style={{ position: "relative" }}>
      <button ref={button} onClick={() => setShown(!shown)}>
        Manti
      </button>{" "}
      {shown && <Tooltip anchor={button} text="Bug'da pishgan" />}
    </p>
  );
}

console.log(
  tooltipPlacement(100, 30, 4),
  tooltipPlacement(10, 30, 4),
);

Konsolda:

text
tepada pastda

Ekranda:

text
Manti Bug'da pishgan

Hisob sof funksiyada — uni sahifasiz sinash mumkin. Komponent faqat o'lchaydi va natijani state'ga beradi. visibility: hidden — element joy egallaydi va o'lchanadi, lekin ko'rinmaydi: birinchi kadr qanday bo'lmasin, foydalanuvchi noto'g'ri joyni ko'rmaydi. Bu misolda tugma sahifa tepasida — haqiqiy brauzerda tooltip pastga tushadi.

10. Real ishda

  • Floating UI (@floating-ui/react-dom), Radix va boshqa UI kutubxonalari tooltip, popover, ochiladigan menyu joylashuvini useLayoutEffect da hisoblaydi — biz ko'rgan izomorf qator aynan shu kutubxonadan.
  • Virtual ro'yxatlar (@tanstack/react-virtual kabi) qatorlar balandligini o'lchashda layout effektga tayanadi (Uzun ro'yxatlar).
  • Chat va izohlar oqimi — yangi xabarda oxirigacha aylantirish, matn maydoni balandligini kontentga moslash.
  • CSS-in-JS kutubxonalari useInsertionEffect ni ishlatadi; ilova dasturchisi uni deyarli yozmaydi.
  • Intervyu savollari: "useEffect va useLayoutEffect farqi nima?", "Qachon useLayoutEffect ishlatasiz?", "Nega hamma joyda useLayoutEffect yozib qo'ya qolmaymiz?", "SSR'da layout effekt nima bo'ladi?".

Xulosa

  • Tartib: render → commit (DOM) → useLayoutEffect → brauzer chizadi → useEffect.
  • useEffect dagi o'lchov foydalanuvchiga noto'g'ri kadrni ko'rsatishi mumkin — o'lchovimizda har safar 1–2 kadr.
  • useLayoutEffect dagi setState chizishdan oldin qayta ishlanadi — miltillash yo'q.
  • Narxi: layout effekt chizishni to'sadi; og'ir ishni u yerga qo'ymang. Avval useEffect.
  • Serverda effektlar ishlamaydi; React 19.3 ogohlantirmaydi, lekin birinchi ko'rinishni o'zingiz o'ylang.
  • useInsertionEffect — CSS-in-JS kutubxonalari uchun: ref yo'q, setState mumkin emas.

Keyingi dars: useId: barqaror va noyob id — label va maydonni, xato matnini bog'lash uchun komponent qayta ishlatilsa ham to'qnashmaydigan id yasaymiz.

Manbalar

  • React: useLayoutEffect — react.dev/reference/react/useLayoutEffect
  • React: useInsertionEffect — react.dev/reference/react/useInsertionEffect
  • React: useEffect (Caveats — bosishdan kelgan effektlar) — react.dev/reference/react/useEffect
  • Floating UI: @floating-ui/react-dom 2.1.9 (npm, 2026-10-13 holati) — floating-ui.com
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React useLayoutEffect: ekran chizilishidan oldin o'lchash va miltillashning oldini olish — IlmHamroh