React / Portal
Portal
React-д component зөвхөн өөрийн parent-н DOM дотор рендерлэгддэг. Гэхдээ заримдаа — modal цонх, tooltip, dropdown гэх мэт — component-ийг DOM-н өөр хэсэгт рендерлэх шаардлага гардаг. Portal яг үүнд зориулагдсан!
Яагаад Portal хэрэгтэй вэ?
CSS-н overflow: hidden эсвэл z-index асуудлаас болж modal цонх буруу харагдах тохиолдол байдаг:
jsx
// ❌ Асуудлыг харцгаая
function Card() {
const [open, setOpen] = useState(false);
return (
// overflow: hidden нь modal-г "тайрдаг"
<div
style={{ overflow: "hidden", height: "100px", border: "1px solid red" }}
>
<button onClick={() => setOpen(true)}>Modal нээх</button>
{open && (
// Энэ modal Card-н overflow-д баригдаж харагдахгүй болно!
<div
style={{
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
background: "white",
padding: "20px",
zIndex: 1000,
}}
>
<p>Modal агуулга</p>
<button onClick={() => setOpen(false)}>Хаах</button>
</div>
)}
</div>
);
}
Portal ашиглан modal-г document.body-д шууд рендерлэвэл энэ асуудал арилна.
createPortal ашиглах нь
react-dom-аас createPortal import хийнэ:
jsx
import { useState } from "react";
import { createPortal } from "react-dom";
// Modal component — document.body дотор рендерлэгдэнэ
function Modal({ children, onClose }) {
return createPortal(
// Рендерлэх JSX
<div
style={{
position: "fixed",
inset: 0,
background: "rgba(0,0,0,0.5)",
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 9999,
}}
onClick={onClose}
>
<div
style={{ background: "white", padding: "24px", borderRadius: "8px" }}
onClick={(e) => e.stopPropagation()}
>
{children}
<button onClick={onClose}>Хаах ✕</button>
</div>
</div>,
// DOM-н аль element-д оруулах вэ?
document.body,
);
}
export default function App() {
const [open, setOpen] = useState(false);
return (
<div
style={{ overflow: "hidden", height: "100px", border: "1px solid #ccc" }}
>
<button onClick={() => setOpen(true)}>Modal нээх</button>
{open && (
<Modal onClose={() => setOpen(false)}>
<h2>Та итгэлтэй байна уу?</h2>
<p>Энэ үйлдлийг буцааж болохгүй.</p>
</Modal>
)}
</div>
);
}
createPortal хоёр аргумент авдаг:
- JSX — рендерлэх агуулга
- DOM element — хаана рендерлэх вэ (ихэвчлэн
document.body)
Бүрэн modal жишээ
Практикт хэрэглэж болох modal component:
jsx
import { useEffect } from "react";
import { createPortal } from "react-dom";
function Modal({ title, children, onClose }) {
// Escape товч дарахад хаах
useEffect(() => {
function handleKey(e) {
if (e.key === "Escape") onClose();
}
document.addEventListener("keydown", handleKey);
// Modal нээлттэй байх үед scroll хаах
document.body.style.overflow = "hidden";
return () => {
document.removeEventListener("keydown", handleKey);
document.body.style.overflow = "";
};
}, [onClose]);
return createPortal(
<div
role="dialog"
aria-modal="true"
style={{
position: "fixed",
inset: 0,
background: "rgba(0,0,0,0.6)",
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 9999,
}}
>
<div
style={{
background: "#fff",
borderRadius: "12px",
padding: "24px",
maxWidth: "480px",
width: "90%",
}}
>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<h2 style={{ margin: 0 }}>{title}</h2>
<button onClick={onClose} aria-label="Хаах">
✕
</button>
</div>
<div style={{ marginTop: "16px" }}>{children}</div>
</div>
</div>,
document.body,
);
}
// Хэрэглэх жишээ
export default function App() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>Мэдээлэл харах</button>
{open && (
<Modal title="Дансны мэдээлэл" onClose={() => setOpen(false)}>
<p>Таны данс амжилттай нэмэгдлээ.</p>
<button onClick={() => setOpen(false)}>Хаах</button>
</Modal>
)}
</>
);
}
Portal-н давуу тал
Portal ашигласнаар:
overflow: hiddenболонz-indexасуудлуудаас ангижирна- Event bubbling хэвийн ажилладаг — Portal-н event нь React-н component tree-г дагаж bubbles хийдэг (DOM tree-г биш)
- Modal, tooltip, dropdown, notification зэрэг UI элементийг аюулгүй зохион байгуулах боломжтой
jsx
// Event bubbling жишээ — Portal дотор дарахад Parent мэдэрнэ
function Parent() {
return (
<div onClick={() => console.log("Parent мэдэрлээ!")}>
<Modal ...>
<button>Дарах</button> {/* Энд дарахад Parent log хэвлэнэ */}
</Modal>
</div>
);
}
Дараагийн хичээлд:
Error Boundary сурна — component-д алдаа гарахад аппыг бүхэлд нь унагааж дуусахаас хамгаалах механизм. Хэрэглэгчид тааламжтай алдааны мэдэгдэл харуулах аргыг эзэмшинэ.