Portals
Advanced React kategorisinin son konusu -- bir component'i, React ağacındaki KONUMUNDAN farklı bir DOM düğümüne render etmenin yolu: Portal'lar.
Portal Nedir? createPortal ile Başlangıç
react-dom'un createPortal fonksiyonu, bir component'i DOM'da farklı
bir yere render etmemizi sağlar:
import { createPortal } from "react-dom";
function BasicPortalExample() {
// createPortal(child, container), `child`'ı normal React ağacındaki
// YERİNE değil, DOM'daki farklı bir düğüme (`container`) render eder --
// burada `document.body`'nin kendisine. Component ağacında (React
// DevTools'ta) hâlâ BasicPortalExample'ın İÇİNDE görünür, ama gerçek
// DOM'da tamamen farklı bir yerdedir.
return createPortal(<p className="tooltip">I'm rendered directly on body!</p>, document.body);
}
createPortal(child, container), child'ı normal React ağacındaki
YERİNE değil, DOM'daki container düğümüne render eder. Component
ağacında (React DevTools'ta) hâlâ olması gereken yerde görünür, ama
gerçek DOM'da tamamen farklı bir konumdadır.
Modal'lar için Portal Kullanmak
Portal'ın en yaygın kullanım alanı modal'lar:
import { useState } from "react";
import { createPortal } from "react-dom";
function Modal({ onClose, children }) {
// Bir modal, Portal'ın en yaygın kullanım alanıdır -- modal'ın CSS'i
// (position: fixed, z-index) sayfanın geri kalanının ÜSTÜNDE görünmesini
// sağlar, ama gerçek DOM konumu ("bir kartın içinde" gibi) bunu bazen
// engelleyebilir (overflow: hidden gibi). Portal, modal'ı doğrudan
// `document.body`'ye render ederek bu sorunu ORTADAN KALDIRIR.
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(event) => event.stopPropagation()}>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>,
document.body,
);
}
function ModalWithPortalExample() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<Modal onClose={() => setIsOpen(false)}>
<p>This is a modal, rendered outside the normal DOM tree.</p>
</Modal>
)}
</div>
);
}
Bir modal'ın CSS'i (position: fixed, yüksek z-index) sayfanın
geri kalanının ÜSTÜNDE görünmesini sağlamalı -- ama modal'ın gerçek DOM
konumu (örneğin overflow: hidden olan bir kartın içi) bunu bazen
engelleyebilir. Portal, modal'ı doğrudan document.body'ye render
ederek bu sorunu ORTADAN KALDIRIR.
Event Bubbling: Portal'ların Şaşırtıcı Davranışı
Portal'ların en önemli (ve en şaşırtıcı) özelliği, event'lerin nasıl davrandığı:
import { useState } from "react";
import { createPortal } from "react-dom";
function Popup() {
return createPortal(<button>Click me (rendered in document.body)</button>, document.body);
}
function EventBubblingThroughPortalExample() {
const [clicks, setClicks] = useState(0);
return (
// ÖNEMLİ: Popup, DOM'da bu <div>'in DIŞINDA (document.body'de)
// render ediliyor. Ama içindeki <button>'a tıklandığında, onClick
// yine de BURADA (React ağacındaki gerçek konumunda) çalışır --
// React, event'leri gerçek DOM ağacına göre değil, KENDİ component
// ağacına göre "bubble" ettirir. Bu, Portal'ların en şaşırtıcı ama en
// kullanışlı özelliği.
<div onClick={() => setClicks(clicks + 1)}>
<p>Clicks: {clicks}</p>
<Popup />
</div>
);
}
Popup, DOM'da dıştaki <div>'in DIŞINDA (document.body'de) render
ediliyor. Ama içindeki butona tıklandığında, onClick yine de dıştaki
<div>'e kadar "bubble" ediyor -- React, event'leri GERÇEK DOM
ağacına göre değil, KENDİ component ağacına göre yayar. Bu, Portal'ları
kullanırken bilmen gereken en önemli davranış.
Portal Hedefini Ayarlamak
document.body yerine, genellikle özel olarak ayrılmış bir hedef
kullanılır:
import { createPortal } from "react-dom";
function Tooltip({ text }) {
// document.body yerine, index.html'de özel olarak ayrılmış bir hedef
// kullanmak daha yaygındır -- örneğin <div id="tooltip-root"></div>,
// uygulamanın #root'una KARDEŞ (sibling) olarak eklenir. Bu, portal
// içeriğinin kendi stillerini/konumunu yönetmesini kolaylaştırır.
const target = document.getElementById("tooltip-root");
if (!target) {
return null;
}
return createPortal(<span className="tooltip">{text}</span>, target);
}
function PortalTargetSetupExample() {
return (
<div>
<p>Hover for more info</p>
<Tooltip text="This tooltip lives in its own DOM node." />
</div>
);
}
index.html'de <div id="tooltip-root"></div> gibi, uygulamanın
#root'una KARDEŞ (sibling) bir eleman eklemek yaygın bir pratiktir --
bu, portal içeriğinin kendi stillerini ve konumunu yönetmesini
kolaylaştırır.
Özet ve Terimler Sözlüğü
createPortal(child, container), bir component'i React ağacındaki
konumunda TUTARAK, gerçek DOM'da farklı bir düğüme render eder -- en
yaygın kullanımı modal'lar, tooltip'ler ve dropdown'lardır (üst
öğelerin overflow: hidden gibi CSS özelliklerinden kaçınmak için).
Event'ler, gerçek DOM konumuna değil, React'in component ağacına göre
bubble eder -- bu, Portal'ları normal component'ler gibi kullanmaya
devam edebilmemizi sağlar.
Terimler Sözlüğü
Portal — Bir component'i, React ağacındaki konumunu koruyarak, DOM'da farklı bir düğüme render etme mekanizması.
Event Bubbling (Olay Yayılımı) — Bir event'in, tetiklendiği elemandan başlayıp ağaçtaki üst elemanlara doğru yayılması.
Pratik Proje
Bu kategoride (React Performance, Error Boundaries, Lazy Loading & Code
Splitting, Suspense, Portals) öğrendiğimiz kavramları bir arada
kullanan, gerçek ve çalıştırılabilir bir örnek proje var:
Advanced React Demo
-- React.memo ile optimize edilmiş bir kurs listesi, bir Error
Boundary, React.lazy + Suspense ile code splitting yapılan bir
detay paneli, ve bir Portal modal'ı bir arada gösteren bir uygulama.
Bilgisayarına indirip çalıştırabilir, kodunu satır satır inceleyebilirsin:
git clone https://github.com/cdurgun/react-course-projects.git
cd react-course-projects
npm install
cd projects/advanced-react
npm run dev
react-course-projects deposu npm workspaces kullanır -- npm install
yalnızca bir kez, depo kökünde çalıştırılır ve tüm proje klasörleri ortak
bağımlılıkları paylaşır (her klasörde ayrı bir node_modules oluşmaz). Kök
dizinde npm install'ı daha önce çalıştırdıysan, doğrudan
cd react-course-projects/projects/advanced-react yapıp npm run dev
demen yeterli.