Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. Muammo: ota-elementning "qamog'i"
- 2.1 Jonli: kesilgan tooltip
- 2.2 Uch "qamoq"
- 3. createPortal
- 3.1 Birinchi portal
- 3.2 Ikki daraxt
- 4. React daraxti ishlaydi
- 4.1 Hodisa — React daraxti bo'ylab
- 4.2 Context — saqlanadi
- 5. Portal qayerga
- 5.1 document.body yoki o'z konteyneri
- 5.2 <dialog> — portal'siz
- 6. Vazifalar qadami: e'lon — toast (holat 10)
- 6.1 Muammo — o'lchangan
- 6.2 Bildirishnoma.tsx
- 6.3 CSS va e2e topgan xato
- 6.4 Testlar va commit
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): qaysi daraxt?
- 2-mashq (o'rta): toast'ni <body> ga chiqaring
- 3-mashq (qiyin): modal ichidagi bosish kartani bosmasin
- 4-mashq: Vazifalar qadami
- 9. Real ishda
- Xulosa
- Manbalar
React portallar: createPortal bilan modal, tooltip va toast'ni DOM'ning boshqa joyiga chizish
Qisqacha:
createPortal(jsx, domElement)— komponentning bir qismini DOM'ning boshqa joyiga (ko'pinchadocument.bodyga) chizadi. Element DOM'da ota-elementlarningoverflow: hidden,transformvaz-indexcheklovlaridan tashqarida bo'ladi — modal, tooltip, toast uchun aynan shu kerak. Lekin React daraxtida u o'z joyida qoladi: state, props, Context ota komponentdan keladi. Hodisalar esa React daraxti bo'ylab yuqoriga ko'tariladi (bubbling) — DOM bo'ylab emas.<dialog>ningshowModal()i esa o'zi "top layer" ga chiqadi — unga portal shart emas.
Bu darsda
overflow: hiddenva stacking context tooltip'ni qanday "qamashini" jonli natijada ko'rasiz.createPortalbilan elementni<body>ga chiqarasiz va u DOM'da qayerda turganini tekshirasiz.- Portal ichidagi bosish React daraxti bo'ylab ota komponentga yetishini va Context saqlanishini ko'rasiz.
- Qachon portal kerak, qachon
<dialog>yetarli — tanlay olasiz. vazifalardagi e'lonni toast'ga aylantirasiz va sahifa siljishini (CLS) o'lchov bilan nolga tushirasiz.
Oldin bilishingiz kerak: useRef II: DOM elementlari, Context I, z-index va stacking context, position: fixed.
1. Nega bu kerak?
«Bahor» menyusida har taom kartasining burchagida "i" belgisi bor. Ustiga kelganda tooltip ochiladi: "Tarkibi: guruch, go'sht, sabzi". Dizayner kartaga overflow: hidden va yumaloq burchak berdi — rasm chiroyli kesilsin deb. Shu bilan tooltip ham kesilib qoldi: faqat yarmi ko'rinadi.
Sardor z-index: 9999 yozdi. Foyda bermadi. Keyin kartaga hover'da transform: scale(1.02) animatsiyasi qo'shildi — endi tooltip qo'shni kartaning ostida qola boshladi. Muammo CSS'da emas, joyda: tooltip DOM'da kartaning ichida turibdi va uning barcha cheklovlarini meros oladi.
Hayotiy o'xshatish: maktabdagi e'lonlar taxtasi. Har sinfda o'z taxtasi bor. Lekin "ertaga dars yo'q" degan e'lonni sinf taxtasiga ilsangiz, uni faqat shu sinf ko'radi. Uni koridordagi umumiy taxtaga ilish kerak. E'lonni baribir sizning sinfingiz yozgan — u sinf rahbaringizning nomidan, sizning sinf ma'lumoti bilan. Faqat osilgan joyi boshqa. Portal — e'lonni koridorga ilish: "kim yozgani" (React daraxti) o'zgarmaydi, "qayerda osilgani" (DOM) — boshqa.
2. Muammo: ota-elementning "qamog'i"
2.1 Jonli: kesilgan tooltip
Avval muammoni React'siz, oddiy HTML va CSS'da ko'ramiz. Chap karta — tooltip uning ichida. O'ng karta — tooltip'ning aynan o'sha matni sahifa darajasida, kartadan tashqarida:
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
.qator { display: flex; gap: 16px; }
.karta {
position: relative; width: 130px; height: 90px;
border: 1px solid #999; border-radius: 12px;
overflow: hidden; padding: 8px; background: #fff7e6;
}
.tooltip {
position: absolute; top: 60px; left: 30px; width: 130px;
background: #222; color: #fff; padding: 6px; font-size: 13px;
}
.tashqarida {
position: absolute; top: 82px; left: 200px; z-index: 1;
}
</style>
<div class="qator">
<div class="karta">Osh (ichida)
<div class="tooltip">Tarkibi: guruch, go'sht, sabzi</div>
</div>
<div class="karta">Osh (portal)</div>
</div>
<div class="tooltip tashqarida">Tarkibi: guruch, go'sht, sabzi</div>Chap kartada tooltip'ning pastki qismi kesilgan — overflow: hidden uni kartaning chegarasida qirqdi. O'ngdagisi to'liq: u kartaning farzandi emas, shuning uchun karta uni cheklay olmaydi. Portal aynan shuni qiladi — React kodida.
2.2 Uch "qamoq"
Ota-element farzandini uch yo'l bilan cheklaydi:
| CSS | Nima qiladi | z-index: 9999 yordam beradimi |
|---|---|---|
overflow: hidden / auto |
chegaradan chiqqan qismni kesadi | yo'q |
transform, filter, opacity < 1 |
yangi stacking context yaratadi (z-index) | yo'q — z-index faqat shu kontekst ichida |
transform |
position: fixed farzandni ekranga emas, o'ziga bog'laydi (position: fixed tuzoqlari) |
yo'q |
Uchinchisi eng kutilmagani: position: fixed toast transform li ota ichida "qotib" qoladi va ekran bilan birga emas, ota bilan birga aylanadi. Barcha uch holatda davo bitta — elementni bu otalardan chiqarish.
3. createPortal
3.1 Birinchi portal
createPortal — react-dom dagi funksiya. U ikki argument oladi: nimani chizish (JSX) va qayerga (DOM element):
import { useEffect } from "react";
import { createPortal } from "react-dom";
function Tooltip({ text }) {
useEffect(() => {
const el = document.getElementById("tooltip");
console.log("DOM'dagi otasi:", el.parentElement.tagName);
}, []);
return createPortal(
<p id="tooltip" role="tooltip">{text}</p>,
document.body,
);
}
export default function App() {
return (
<div style={{ overflow: "hidden" }}>
<h1>Osh — 35000 so'm</h1>
<Tooltip text="Tarkibi: guruch, go'sht, sabzi" />
</div>
);
}Konsolda:
DOM'dagi otasi: BODYEkranda:
Osh — 35000 so'mIkki narsa. Tooltip JSX'da div ning ichida yozilgan, lekin DOM'da uning otasi — <body>. Va «Ekranda» bo'limida tooltip yo'q: sinov vositasi faqat ilova ildizi (#root) ichini o'qiydi, tooltip esa undan tashqarida. Natija oynasida u ko'rinadi — sahifaning pastida, <body> oxirida.
createPortal ni komponent return qiladi — xuddi oddiy JSX kabi. Shuning uchun portal ham komponentning bir qismi: shartli chizish, key, ro'yxat — hammasi ishlaydi.
3.2 Ikki daraxt
Portal bilan ilovada ikki xil "daraxt" ajraladi:
flowchart LR
subgraph React["React daraxti"]
A1["App"] --> D1["div"]
D1 --> H1["h1"]
D1 --> T1["Tooltip"]
end
subgraph DOM["DOM daraxti"]
B["body"] --> R["div#root"]
R --> D2["div"]
D2 --> H2["h1"]
B --> T2["p#tooltip"]
endChapda — React daraxti: Tooltip — div ning bolasi. O'ngda — DOM: p#tooltip — body ning bolasi, #root dan tashqarida. CSS DOM daraxtiga qaraydi (kesish, stacking). React esa o'z daraxtiga qaraydi — state, Context, hodisalar. Keyingi bo'limlar shu ikki gapni isbotlaydi.
Tekshirib ko'ring: Portal bilan chizilgan tooltip
document.querySelector("#root p")bilan topiladimi?
Javob
Yo'q. #root p — #root ichidagi <p> ni qidiradi, tooltip esa DOM'da <body> ning bevosita bolasi. Testlarda ham shu tuzoq: komponent ildiz konteynerida emas, document.body da qidiriladi. React Testing Library'ning screen.getByText butun document.body ni qidiradi — shuning uchun portal'ni topadi (RTL I).
4. React daraxti ishlaydi
4.1 Hodisa — React daraxti bo'ylab
Portal ichidagi tugma bosildi. DOM'da uning ota-bobolari: <body>, <html>. React'da esa — Toast, div, App. Hodisa qaysi yo'ldan ko'tariladi?
import { createPortal } from "react-dom";
function Toast() {
return createPortal(
<p>
Buyurtma qabul qilindi <button>Yopish</button>
</p>,
document.body,
);
}
export default function App() {
function handleClick(e) {
console.log("App'dagi div ushladi:", e.target.textContent);
}
return (
<div onClick={handleClick}>
<h1>Buyurtmalar</h1>
<Toast />
</div>
);
}Konsolda:
App'dagi div ushladi: YopishTugma DOM'da div ning ichida emas — lekin div ning onClick i ishladi. React hodisalarni o'z daraxti bo'ylab ko'taradi (Hodisalarni boshqarish). Ko'pincha bu qulay: modal ichidagi bosishni ota komponent ham bilishi mumkin.
Ba'zan esa tuzoq. Masalan, "kartaning istalgan joyini bossa — taom sahifasiga o'tadi" degan onClick bor, ichida esa portal bilan ochilgan modal. Modaldagi «Bekor qilish» bosilsa — karta ham "bosildi" deb sahifa almashadi. Davosi — modal ichida e.stopPropagation() yoki portalni kartaning tashqarisida chizish.
addEventListener bilan DOM'ga qo'yilgan oddiy tinglovchi esa DOM yo'lidan boradi: document.body dagi tinglovchi tugma bosilishini ko'radi, #root dagisi — yo'q.
4.2 Context — saqlanadi
import { createContext, use } from "react";
import { createPortal } from "react-dom";
const ThemeContext = createContext("yorug'");
function Modal() {
const theme = use(ThemeContext);
console.log("Modal temasi:", theme);
return createPortal(<p>Modal ({theme})</p>, document.body);
}
export default function App() {
return (
<ThemeContext value="qorong'i">
<h1>Menyu</h1>
<Modal />
</ThemeContext>
);
}Konsolda:
Modal temasi: qorong'iDOM'da Modal ning <p> i ThemeContext provayderidan "uzoqda" — #root dan tashqarida. Lekin Context React daraxti bo'ylab keladi (Context I) — Modal uni oldi. Shuning uchun portal ichidagi komponent ilovaning boshqa qismlari kabi ishlaydi: tema, til, foydalanuvchi — hammasi bor.
| Narsa | Qaysi daraxt bo'yicha |
|---|---|
| CSS (kesish, stacking, meros stillar) | DOM |
| state, props, Context | React |
React hodisalari (onClick) |
React |
addEventListener |
DOM |
document.querySelector |
DOM |
Jadvaldagi birinchi qator haqida bir ogohlantirish: meros CSS ham DOM bo'yicha. Kartaning color yoki font-size i portalga o'tmaydi — u <body> ning stillarini oladi. Modal uchun stillarni alohida yozing.
5. Portal qayerga
5.1 document.body yoki o'z konteyneri
Eng oddiy nishon — document.body. Ba'zi loyihalar index.html ga alohida konteyner qo'yadi:
<body>
<div id="root"></div>
<div id="modal-root"></div>
</body>createPortal(<Modal />, document.getElementById("modal-root"));Afzalligi — barcha modallar bir joyda, CSS bilan boshqarish oson. document.body esa hech qanday sozlamasiz ishlaydi. Ikkalasi ham to'g'ri — loyihada bittasini tanlang.
5.2 <dialog> — portal'siz
<dialog> elementini showModal() bilan ochsangiz, brauzer uni top layer ga chiqaradi — barcha stacking context'lardan yuqori maxsus qatlam (Top layer). overflow: hidden ham, z-index ham unga ta'sir qilmaydi. Ya'ni modal oyna uchun portal ko'pincha shart emas: <dialog> ni istalgan joyda chizib, ref orqali showModal() chaqirsangiz bas (useRef II). vazifalar dagi o'chirish dialogi aynan shunday — portal'siz.
| Element | Yechim |
|---|---|
| modal oyna (fokus qamoqda, fon bloklangan) | <dialog> + showModal() — top layer |
| toast, bildirishnoma | portal (position: fixed) |
| tooltip, ochiladigan menyu | portal yoki Popover API |
| eski brauzerlar uchun modal | portal |
Portal faqat "qayerga" muammosini hal qiladi. Fokusni modalga olib kirish, Esc bilan yopish, fokusni qaytarish — bular sizning ishingiz (Fokus boshqaruvi). <dialog> ularning bir qismini o'zi qiladi.
Tekshirib ko'ring: Toast
position: fixed; bottom: 1rembilan yozilgan, lekin u ekranning pastida emas, sahifa o'rtasidagi kartaning pastida turibdi. Kartadatransform: scale(1)bor. Nega va qanday tuzatiladi?
Javob
transform li element position: fixed farzandlari uchun "o'rovchi blok" bo'ladi — fixed endi ekranga emas, kartaga nisbatan. Tuzatish: toast'ni portal bilan <body> ga chiqaring — u kartaning farzandi bo'lmaydi va fixed yana ekranga bog'lanadi.
6. Vazifalar qadami: e'lon — toast (holat 10)
6.1 Muammo — o'lchangan
vazifalar da «qo'shildi», «o'chirildi» kabi e'lonlar <p id="elon" role="status"> da chiqardi — oqim ichida, eksport tugmalari ustida. E'lon paydo bo'lganda va 3 soniyadan keyin o'chganda, uning ostidagi hamma narsa siljiydi. Kanonni qurishda bu o'lchandi (olchov/elon-cls.mjs, layout-shift yozuvlari, Chrome 155, 3 marta):
| Kiritishsiz siljish (e'lon o'chdi) | Kiritishdan keyingi siljish | |
|---|---|---|
| oqim ichida (holat 09) | 0.0088 (3/3) | 0.0279 |
portal + fixed (holat 10) |
0.0000 (3/3) | 0.0167 |
CLS (Cumulative Layout Shift) — sahifa elementlari kutilmaganda qancha siljiganini o'lchaydigan Core Web Vitals ko'rsatkichi. Foydalanuvchi hech narsa qilmaganda siljish — eng yomoni: u bosmoqchi bo'lgan tugma qochadi. Kiritishdan keyingi siljish 0.0167-bob — yangi qatorning o'zi, u kutilgan.
- Oqim ichida (v7 uslubi)0,0088
- Portal + fixed (v8)0
Manba: vazifalar kanoni: natijalar/10-elon-cls-oldin.txt va 10-elon-cls-keyin.txt, Chrome 155, 3 marta
6.2 Bildirishnoma.tsx
Kanondan aynan (izohlar telefon uchun qayta bo'lingan):
// Bildirishnoma.tsx — e'lon (toast) ekranning pastida (18/#10).
// Portal: element React daraxtida App ichida (state, context,
// hodisalar — App orqali), DOM'da esa <body> ning oxirida —
// #ilova va <main> dan tashqarida: max-width, overflow va
// ota-elementlarning stacking context'i (z-index, transform) unga
// ta'sir qilmaydi. position: fixed — oqimdan chiqadi: matn paydo
// bo'lib, o'chganda sahifa siljimaydi (o'lchandi: oqim ichida
// CLS 0.0088 — natijalar/10-*)
import { createPortal } from "react-dom";
type Props = {
matn: string;
};
export function Bildirishnoma({ matn }: Props) {
// role="status" — jonli hudud doim DOM'da (bo'sh bo'lsa ham):
// ekran o'quvchi faqat oldindan bor hududdagi o'zgarishni o'qiydi
return createPortal(
<p id="elon" role="status" className="bildirishnoma">
{matn}
</p>,
document.body,
);
}App'da bitta qator almashdi:
- <p id="elon" role="status">
- {elon}
- </p>
+ <Bildirishnoma matn={elon} />elon — App'ning state'i (aniqrog'i, useOptimistic dagi optimistik e'lon). Portal uni props orqali oladi — React daraxti ishlaydi. DOM'da esa <p> <body> oxirida.
Ikki a11y qarori izohda:
role="status"doim DOM'da. Bo'sh bo'lsa ham<p>chiziladi. Ekran o'quvchi faqat oldindan bor jonli hududdagi o'zgarishni o'qiydi (Jonli hududlar). Hudud matn bilan birga paydo bo'lsa, Malika e'lonni eshitmasligi mumkin.- Bo'sh paytda —
opacity: 0,hiddenemas. Yashirilgan (display: none) hudud ham ishonchsiz.
6.3 CSS va e2e topgan xato
/* 18/#10: e'lon — toast (portal orqali <body> da), oqimdan
tashqarida */
.bildirishnoma {
position: fixed;
inset-inline: 1rem;
bottom: 1rem;
z-index: 1;
max-width: 34rem;
margin: 0 auto;
padding: 0.75rem 1rem;
border: 1px solid var(--rang-chegara);
border-radius: 0.5rem;
background: var(--rang-yuza);
color: var(--rang-matn);
box-shadow: 0 0.25rem 1rem rgb(0 0 0 / 0.2);
transition: opacity 0.2s;
/* Faqat xabar (tugmasi yo'q): ostidagi qatorni bosishga xalaqit
bermasin — 18/#10 da e2e topdi: toast o'chirish tugmasini yopib
qo'ydi */
pointer-events: none;
}
.bildirishnoma:empty { opacity: 0; }
#qoshish-tugma { min-width: 9.5em; }Kanondan; oxirgi ikki qoidaning izohlari qisqartirildi (ular pastda matnda).
Uch qator alohida hikoya:
pointer-events: none— brauzer tekshiruvi (e2e) topgan xato. Toast ekran pastida, oxirgi vazifa qatorining ustida turardi va o'chirish tugmasini yopib qo'ydi. Ssenariy tugmani bosdi — bosishni toast oldi (natija-10-toast-ustida.txt). Toast'da tugma yo'q — u bosishni ostidagi elementga o'tkazib yuborsin.:empty { opacity: 0 }— bo'sh toast ko'rinmaydi, lekin jonli hudud joyida.#qoshish-tugma { min-width }— o'lchovning ikkinchi topilmasi: "Qo'shish" → "Qo'shilmoqda…" (useFormStatus) tugmani kengaytirib, maydonni 0.0002 ga siljitardi.
Portal hamma narsani hal qilmaydi: toast endi boshqa elementlarning ustida. Ustidagi element — bosish, o'qish va fokus uchun to'siq. Har toast'da shu savolni bering: "ostidagi tugmani yopmaydimi?".
6.4 Testlar va commit
e2e ssenariyda yana bir o'zgarish topildi (6-o'zgarish): server 500 ni 17–29 ms da qaytaradi va optimistik katak shuncha turadi. Ssenariy esa uni alohida so'rov bilan, kech o'qirdi. Endi katak holati bosish bilan bir vazifada yoziladi. Sabab o'lchandi (10-optimistik-vaqt.txt) — v7 da ham xuddi shu oyna bor edi.
git switch -c fix/elon-portal
git add -A
git commitCommit (kanon): fix: e'lon — portal orqali <body> da, sahifa siljimaydi. Tekshiruvlar: Vitest Tests 37 passed (37) (+1), node:test 163, lint, format, tip — toza. Brauzer — 159/159 (yana ikki marta ishga tushirilganda ham 159), e2e-18 — 29/29. Diff: 4 fayl, +61 −3.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| tooltip kesilgan | otada overflow: hidden |
portal bilan <body> ga |
z-index: 9999 ishlamaydi |
otada transform/opacity — yangi stacking context |
portal yoki <dialog> (top layer) |
fixed toast sahifa bilan "suzadi" |
transform li ota |
portal |
| modal ichidagi bosish kartani ham "bosdi" | hodisa React daraxti bo'ylab ko'tarildi | e.stopPropagation() yoki portalni boshqa joyda chizish |
| modal stillari "yo'qoldi" | meros CSS DOM bo'yicha — <body> dan |
modal uchun alohida stil |
| testda portal topilmadi | ildiz konteynerida qidirilgan | document.body / screen bilan qidirish |
| toast ostidagi tugma bosilmaydi | toast ustida turibdi | pointer-events: none (tugmasiz toast) |
| ekran o'quvchi e'lonni o'qimadi | jonli hudud matn bilan birga paydo bo'ldi | hudud doim DOM'da, faqat matni o'zgaradi |
8. Mashqlar
Mashqlar kurs/mashqlar/18/10-portal/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): qaysi daraxt?
Portal ichidagi komponent uchun React yoki DOM deb yozing:
- Context qiymatini qaysi daraxt bo'yicha oladi?
overflow: hiddenuni qaysi daraxt bo'yicha kesadi?- Ichidagi tugma bosilsa,
onClickqaysi daraxt bo'ylab ko'tariladi? - Ota-elementdagi
colorstili qaysi daraxt bo'yicha meros o'tadi?
Yechim
- React — Context React daraxti bo'ylab tushadi.
- DOM — CSS DOM'dagi ota-bolalikni ko'radi; portal otaning bolasi emas.
- React — React hodisalari React daraxti bo'ylab ko'tariladi.
- DOM — meros CSS ham DOM bo'yicha: portal
<body>stillarini oladi.
2-mashq (o'rta): toast'ni <body> ga chiqaring
Toast hozir App ning ichida chiziladi. Uni createPortal bilan document.body ga chiqaring va useEffect da otasini konsolga yozing: toast otasi: BODY. Toast'ga role="status" bering. Ishora: «Birinchi portal» bo'limi.
import { useEffect } from "react";
function Toast({ text }) {
useEffect(() => {
const el = document.getElementById("toast");
console.log("toast otasi:", el.parentElement.tagName);
}, []);
return <p id="toast">{text}</p>;
}
export default function App() {
return (
<main style={{ overflow: "hidden" }}>
<h1>Buyurtmalar</h1>
<Toast text="3-stol buyurtmasi qabul qilindi" />
</main>
);
}Konsolda:
toast otasi: MAINYechim
import { useEffect } from "react";
import { createPortal } from "react-dom";
function Toast({ text }) {
useEffect(() => {
const el = document.getElementById("toast");
console.log("toast otasi:", el.parentElement.tagName);
}, []);
return createPortal(
<p id="toast" role="status">
{text}
</p>,
document.body,
);
}
export default function App() {
return (
<main style={{ overflow: "hidden" }}>
<h1>Buyurtmalar</h1>
<Toast text="3-stol buyurtmasi qabul qilindi" />
</main>
);
}Konsolda:
toast otasi: BODYEkranda:
BuyurtmalarToast endi <main> ning overflow: hidden idan tashqarida. «Ekranda» da yo'q — u #root dan tashqarida; natija oynasida esa sahifa oxirida ko'rinadi. Toast ning props, effekti — hammasi o'zgarishsiz ishladi.
3-mashq (qiyin): modal ichidagi bosish kartani bosmasin
Taom kartasini bossangiz — "Taom sahifasi ochildi" yoziladi. Kartadagi «Tarkibi» tugmasi portal bilan modal ochadi. Muammo: modaldagi «Yopish» bosilganda karta ham "bosildi". Tuzating: «Yopish» modalni yopsin, lekin kartaning onClick i ishlamasin. Ishora: «Hodisa — React daraxti bo'ylab» bo'limi.
import { useState } from "react";
import { createPortal } from "react-dom";
function IngredientsModal({ onClose }) {
return createPortal(
<div role="dialog" aria-label="Tarkibi">
<p>Guruch, go'sht, sabzi</p>
<button onClick={onClose}>Yopish</button>
</div>,
document.body,
);
}
export default function App() {
const [open, setOpen] = useState(false);
return (
<div onClick={() => console.log("Taom sahifasi ochildi")}>
<h2>Osh</h2>
<button
onClick={(e) => {
e.stopPropagation();
setOpen(true);
}}
>
Tarkibi
</button>
{open && <IngredientsModal onClose={() => setOpen(false)} />}
</div>
);
}Konsolda:
Taom sahifasi ochildiYechim
import { useState } from "react";
import { createPortal } from "react-dom";
function IngredientsModal({ onClose }) {
return createPortal(
// Modal ichidagi hech bir bosish kartaga ko'tarilmasin
<div
role="dialog"
aria-label="Tarkibi"
onClick={(e) => e.stopPropagation()}
>
<p>Guruch, go'sht, sabzi</p>
<button onClick={onClose}>Yopish</button>
</div>,
document.body,
);
}
export default function App() {
const [open, setOpen] = useState(false);
return (
<div onClick={() => console.log("Taom sahifasi ochildi")}>
<h2>Osh</h2>
<button
onClick={(e) => {
e.stopPropagation();
setOpen(true);
}}
>
Tarkibi
</button>
{open && <IngredientsModal onClose={() => setOpen(false)} />}
</div>
);
}Ekranda:
Osh
TarkibiKonsol bo'sh. «Yopish» ning bosilishi React daraxti bo'ylab avval modal div iga yetdi va u yerda to'xtadi — kartaga bormadi. stopPropagation ni modalning ildizida qo'ydik: modaldagi istalgan tugma xavfsiz. «Tarkibi» tugmasidagi stopPropagation esa boshlang'ich kodda ham bor edi — u modal ochilishi kartani bosmasligi uchun.
4-mashq: Vazifalar qadami
fix/elon-portal branch'ida Bildirishnoma.tsx ni yarating, App'da <p id="elon"> ni almashtiring va CSS'ni qo'shing. Vitest testi: e'lon document.body ning bolasi va role="status". Commit qiling va PR oching.
Yechim
Tekshiruv ro'yxati:
- DevTools → Elements:
<p id="elon">—<body>ning oxirida,#ilovadan tashqarida. - DevTools → Performance → Layout shifts: vazifa qo'shing va 3 soniya kuting — e'lon o'chganda siljish yo'q.
- Ro'yxat oxirgi qatorining «O'chirish» tugmasini e'lon ko'rinib turganda bosing — bosiladi (
pointer-events: none). npm test— Vitest 37. Commit —fix: e'lon — portal orqali <body> da, sahifa siljimaydi.
9. Real ishda
- UI kutubxonalari — Radix, MUI, Headless UI modal, tooltip, dropdown'larni portal bilan chizadi. Ularda
Portalkomponenti vacontainerprop'i bor: "qayerga chizish" ni o'zgartirish uchun. - Toast kutubxonalari (Sonner, react-hot-toast) — bitta
<Toaster />portal orqali<body>da, xabarlar esa istalgan komponentdan keladi. - Mikro-frontend va "orol"lar — portal bilan React bir ildizdan turib, sahifaning boshqa joylaridagi konteynerlarga ham chiza oladi.
- Intervyu savollari: "Portal nima va qachon kerak?", "portal ichidagi hodisa qaysi yo'ldan ko'tariladi?", "nega
z-index: 9999ishlamadi?", "modal uchun portal shartmi?".
Xulosa
createPortal(jsx, element)— DOM'da boshqa joyga chizadi; ota-elementningoverflow, stacking vatransformcheklovlari yo'qoladi.- React daraxtida portal o'z joyida: state, props, Context, React hodisalari — ota komponent orqali.
- CSS, meros stillar,
querySelector,addEventListener— DOM daraxti bo'yicha. <dialog>+showModal()— top layer, portal shart emas; toast va tooltip — portal.vazifalar: e'lon —Bildirishnomaportali,fixed— CLS 0.0088 → 0; e2e topganpointer-eventstuzog'i.
Keyingi dars: <Activity> — UI'ni yashirganda uning state'ini saqlash va keyingi ekranni oldindan chizish.
Manbalar
- React:
createPortal— react.dev/reference/react-dom/createPortal (2026-10 holati, React 19.3) - MDN: "Stacking context" — developer.mozilla.org/docs/Web/CSS/CSS_positioned_layout/Stacking_context
- MDN:
<dialog>va top layer — developer.mozilla.org/docs/Web/HTML/Element/dialog - web.dev: "Cumulative Layout Shift (CLS)" — web.dev/articles/cls
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!