Portals — modal, tooltip uchun, DOM dan tashqariga chiqish
Muammodan boshlaylik
Mahsulot kartochkasida "O'chirish" tugmasi tasdiqlash modalini ochadi:
function MahsulotKartochka({ mahsulot }) {
const [ochiq, setOchiq] = useState(false);
return (
<article className="kartochka">
<h3>{mahsulot.nomi}</h3>
<button onClick={() => setOchiq(true)}>O'chirish</button>
{ochiq && <Modal onYopish={() => setOchiq(false)}>Ishonchingiz komilmi?</Modal>}
</article>
);
}
.modal-fon {
position: fixed;
inset: 0;
z-index: 1000;
}
Kutilgan: butun ekranni qoplovchi modal. Haqiqatda:
- Modal kartochka ichida qisilib qoldi — kartochkada
overflow: hiddenbor z-index: 1000ga qaramay, modal qo'shni kartochka ostida — kartochkadatransformbor va u yangi "stacking context" yaratgan (CSS kursi, 11-dars)position: fixedishlamayapti — ota-onadagitransformuni ekranga emas, kartochkaga nisbatan joylashtiradi
Muammo CSS da emas, DOM dagi joylashuvda: modal kartochkaning ichida, va ota-onaning stillari unga ta'sir qiladi.
createPortal
Portal — komponentni React daraxtidagi joyida qoldirib, uni DOM ning boshqa joyiga render qilish:
import { createPortal } from 'react-dom';
function Modal({ children, onYopish }) {
return createPortal(
<div className="modal-fon" onClick={onYopish}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
{children}
<button onClick={onYopish}>Yopish</button>
</div>
</div>,
document.body // ← shu yerga render qilinadi
);
}
createPortal(jsx, domElement):
jsx— nima render qilinadidomElement— DOM ning qayeriga
React daraxti: DOM:
App <body>
└── MahsulotKartochka <div id="root">
└── Modal ─────────────┐ <article class="kartochka">...</article>
│ </div>
└────────▶ <div class="modal-fon">...</div> ← body ning oxirida
</body>
Endi modal body ning to'g'ridan-to'g'ri bolasi: ota-onaning overflow, transform va z-index i unga ta'sir qilmaydi.
Alohida konteyner
<!-- index.html -->
<body>
<div id="root"></div>
<div id="modal-root"></div>
</body>
createPortal(<ModalKontent />, document.getElementById('modal-root'));
Barcha modal, tooltip va bildirishnomalar bitta joyda — DevTools da topish oson.
Portal — React uchun oddiy bola
DOM da modal boshqa joyda, lekin React uchun u hali ham MahsulotKartochka ning bolasi:
1. Context ishlaydi
<ThemeContext.Provider value="qorongi">
<MahsulotKartochka /> {/* ichidagi Modal ham "qorongi" ni oladi */}
</ThemeContext.Provider>
2. Props va state — odatdagidek
Modal mahsulot ni props orqali oladi, setOchiq bilan yopiladi — hech qanday farq yo'q.
3. Hodisalar React daraxti bo'ylab ko'tariladi
<div onClick={() => console.log('Kartochka bosildi')}>
<Modal>...</Modal> {/* modal ichidagi bosish ham bu yerga yetib keladi! */}
</div>
DOM da modal kartochka ichida emas, lekin React hodisalari (onClick, onKeyDown) React daraxti bo'yicha ko'tariladi (bubbling). Modal ichidagi bosish kartochkaning onClick ini ham ishga tushiradi.
[!WARNING] Bu kutilmagan xatolarga olib kelishi mumkin: kartochka butunligicha havola bo'lsa (
onClick={() => navigate(...)}), modal ichidagi istalgan bosish sahifani almashtiradi. Kerak bo'lsa modal ildizidaonClick={(e) => e.stopPropagation()}qiling.
To'liq modal — accessibility bilan
Portal faqat joylashuv muammosini hal qiladi. Haqiqiy modal uchun yana kerak (HTML kursi, 21–22-darslar): fokusni ichkarida ushlash, Esc bilan yopish, yopilganda fokusni qaytarish, orqadagi sahifani bloklash.
Eng oson yo'l — HTML ning <dialog> elementi:
import { useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
function Modal({ ochiq, onYopish, sarlavha, children }) {
const ref = useRef(null);
useEffect(() => {
const dialog = ref.current;
if (ochiq && !dialog.open) dialog.showModal();
if (!ochiq && dialog.open) dialog.close();
}, [ochiq]);
return createPortal(
<dialog
ref={ref}
onClose={onYopish} // Esc bosilganda ham
aria-labelledby="modal-sarlavha"
className="modal"
>
<h2 id="modal-sarlavha">{sarlavha}</h2>
{children}
<button onClick={onYopish}>Yopish</button>
</dialog>,
document.body
);
}
showModal() o'zi beradi: fokus ichkarida, orqa fon inert, Esc bilan yopish, yopilganda fokus qaytadi, ::backdrop bilan fon (HTML kursi, 22-dars). <dialog> "top layer" da ko'rsatiladi — z-index muammolari umuman yo'q.
[!INFO]
<dialog>top layer ga chiqqani uchun z-index muammosini portal siz ham hal qiladi. Portal baribir foydali: modal DOM da sahifa kontentidan ajralgan bo'ladi, va ota-onadagi CSS (masalan,display: noneyokicontent-visibility) unga ta'sir qilmaydi.
Tooltip va dropdown
function Tooltip({ matn, children }) {
const [joy, setJoy] = useState(null);
const ref = useRef(null);
function korsat() {
const r = ref.current.getBoundingClientRect();
setJoy({ top: r.bottom + 8, left: r.left + r.width / 2 });
}
return (
<>
<span
ref={ref}
onMouseEnter={korsat}
onMouseLeave={() => setJoy(null)}
onFocus={korsat}
onBlur={() => setJoy(null)}
aria-describedby={joy ? 'tooltip' : undefined}
>
{children}
</span>
{joy &&
createPortal(
<div
id="tooltip"
role="tooltip"
className="tooltip"
style={{ position: 'fixed', top: joy.top, left: joy.left, translate: '-50% 0' }}
>
{matn}
</div>,
document.body
)}
</>
);
}
Tooltip overflow: hidden li jadval yoki scroll konteyner ichida bo'lsa ham — to'liq ko'rinadi.
[!TIP] Tooltip va dropdown larning joylashuvi murakkab: ekran chetiga yaqinlashganda tomonni almashtirish, scroll da kuzatish, o'lchamlarni hisoblash. Buning uchun Floating UI kutubxonasi bor; Radix UI, Headless UI kabi komponent kutubxonalari esa portal, joylashuv va accessibility ni tayyor holda beradi. Shuningdek, brauzerlarda yangi Popover API (
popoveratributi) va CSS anchor positioning paydo bo'lmoqda.
Qachon portal kerak
| ✅ Portal | ❌ Kerak emas |
|---|---|
| Modal va dialog oynalar | Oddiy ochiladigan bloklar (akkordeon) |
Tooltip, popover, dropdown menyular (overflow li konteynerda) |
Element o'z joyida to'g'ri ko'rinsa |
| Toast bildirishnomalar | |
| Sahifadan tashqaridagi DOM ga render (masalan, boshqa oyna, iframe) |
Nima uchun shunday?
Nima uchun portal dagi hodisalar DOM bo'yicha emas, React daraxti bo'yicha ko'tariladi?
Chunki bu komponentlarni inkapsulyatsiya qiladi. Modal komponenti MahsulotKartochka ning ichki qismi — u qayerga render qilinishi uning implementatsiya tafsiloti. Agar hodisalar DOM bo'yicha ko'tarilsa, portal ishlatish yoki ishlatmaslik komponentning xatti-harakatini o'zgartirardi: bir kun createPortal qo'shsangiz, ota-onadagi onKeyDown yoki onClick lar jimgina ishlamay qo'yardi. React izchil qoida tanlagan: hamma narsa — Context, props, hodisalar — React daraxti bo'yicha ishlaydi. DOM dagi joylashuv esa faqat ko'rinish masalasi. Bu sizga komponentni ichini o'zgartirmasdan uni ota-onaning CSS cheklovlaridan "qochirish" imkonini beradi.
Xulosa
Eslab qoling:
- Muammo: ota-onadagi
overflow: hidden,transform,z-indexmodal va tooltip larni buzadi createPortal(jsx, domElement)— React daraxtida joyida, DOM da boshqa joyda- Odatda
document.bodyyoki alohida#modal-root - React uchun portal — oddiy bola: Context, props, state ishlaydi
- Hodisalar React daraxti bo'ylab ko'tariladi — kerak bo'lsa
stopPropagation - Modal uchun — portal +
<dialog>+showModal(): fokus, Esc, inert bepul - Tooltip/dropdown joylashuvi uchun — Floating UI yoki tayyor komponent kutubxonalari
Keyingi dars
81-dars: Suspense — data fetching, skeleton UI