Bir portal, bir bileşenin bir kısmını DOM'da farklı bir yere, genellikle document.body'ye render eder; bileşen ise React ağacında aynı yerde kalır. react-dom'dan createPortal(children, domNode) ile bir tane oluşturursun. Portal'lar, modalların, tooltip'lerin ve açılır menülerin overflow: hidden'lı ya da kendi katmanlama bağlamı olan bir ebeveynden kaçma yoludur.
Aşağıdaki kutu dışına taşan her şeyi kırpar. İki ipucunu da aç.
Normal ipucu kesik çizgili kenarlıkta kesilir. Portal ipucu tam olarak görünür, çünkü DOM düğümü kutunun değil <body>'nin bir çocuğudur. Kutudan overflow: 'hidden''ı sil; normal ipucu artık kırpılmaz.
Sözdizimi
import { createPortal } from 'react-dom';
createPortal(children, domNode, key?)
childrenherhangi bir JSX'tir: bir öğe, bir fragment, bir bileşen.domNode,document.bodyya dadocument.getElementById('modal-root')gibi mevcut bir DOM öğesidir. Portal render olduğunda var olmalıdır.keyisteğe bağlıdır; bir portal listesi render ettiğinde kullanılır.
createPortal, JSX'ine diğer her öğe gibi koyduğun bir şey döndürür. Ebeveynin kendi DOM konumunda hiçbir şey render etmez.
document.body içinde bir modal
Modal klasik örnektir. transform'lu bir kartın içinde, position: fixed bir katman pencereye değil o karta göre konumlanır ve kartın overflow: hidden'ı da onu kırpar. document.body içine render edildiğinde tüm görünümü kaplar.
Modalı aç, sonra kapatmak için Escape'e bas ya da karanlık katmana tıkla. Açıldığında odak Close düğmesine geçer. Şimdi katmanı createPortal olmadan döndür (çağrıyı ve document.body argümanını kaldır): katman kartın boyutuna küçülür, çünkü transform kartı sabit öğeler için kapsayıcı blok yapar.
Olaylar React ağacı boyunca kabarcıklanır
Bir portal, bileşenin nerede yaşadığını değil, DOM düğümünün nerede yaşadığını değiştirir. React olayları React ağacı boyunca yukarı kabarcıklanır, bu yüzden bir portal içindeki tıklama, DOM'da düğme <body>'nin bir çocuğu olsa bile onu render eden bileşenin onClick'ine ulaşır.
Yerel dinleyiciler farklıdır. addEventListener ile eklenen bir dinleyici DOM ağacını izler ve tıklamayı asla görmez.
"Inside the wrapper"a tıkla: hem React işleyicisi hem yerel dinleyici kayıt yazar. "In a portal"a tıkla: yalnızca React işleyicisi kayıt yazar. Portal düğmesi ekranda kesik çizgili sarmalayıcının dışında durur, ama React onu yine de bir çocuk olarak ele alır.
Genellikle istediğin budur: bir menünün ebeveynindeki onClick portal öğelerindeki tıklamaları görür ve bileşenin üstündeki context provider'ları portal içinde de geçerlidir. "Dışarı tıklayınca kapat" mantığında seni şaşırtabilir. Bir menüyü kapatan bir sarmalayıcıdaki onClick, menünün portal'ı içindeki tıklamalar için de tetiklenir; bu yüzden menünün içinde yayılımı durdur (e.stopPropagation(), olaylar sayfasında anlatılıyor) ya da yerel bir document dinleyicisi kullanıp hedefin portal'ın DOM düğümünün içinde olup olmadığını kontrol et.
Kendi kapsayıcına portal'lar
document.body en basit hedeftir. Bazı uygulamalar tüm katmanların tek bir yeri ve tek bir katman sırasını paylaşması için index.html'e özel bir kapsayıcı ekler:
<body>
<div id="root"></div>
<div id="modal-root"></div>
</body>
createPortal(<Modal />, document.getElementById('modal-root'));
Sunucu tarafı render ile (Next.js ve diğer framework'ler) document sunucuda yoktur. Portal'ı yalnızca bileşen mount olduktan sonra render et; örneğin bir efektin true yaptığı bir mounted state'inin arkasında.
Portal'ın tetikleyicisinin yanında konumlanması gerektiğinde önce tetikleyiciyi ölç. İlk örnek onu tıklama işleyicisinde ölçer; titrememesi gereken bir tooltip için tarayıcı boyamadan önce çalışan useLayoutEffect içinde ölç.
Erişilebilir modallar
İşaretlemeyi document.body'ye taşımak tek başına klavye ve ekran okuyucu kullanıcıları için hiçbir şey yapmaz. Bir modalın ayrıca şunlara ihtiyacı vardır:
role="dialog"vearia-modal="true", başlığını gösterenaria-labelledbyile.- Açıldığında odağın diyaloğa taşınması (örnek Close'a odaklanır) ve kapandığında onu açan düğmeye geri dönmesi.
- Kapatmak için Escape.
- Açıkken odağın içeride tutulması, böylece Tab arkadaki sayfaya geçmez. Modal açıkken uygulama köküne
inertözelliğini ayarlamak oradaki odağı ve tıklamaları engeller.
Yerel <dialog> öğesi bunun çoğunu senin için halleder. dialogRef.current.showModal() ile açıldığında tarayıcının üst katmanında, her z-index'in üstünde çizilir, sayfanın geri kalanını inert yapar ve Escape ile kapanır. Portal'a ihtiyaç duymaz, bu yüzden basit onay diyalogları için iyi bir varsayılandır; portal'lar ise tooltip'ler, menüler ve özel katmanlar için araç olmaya devam eder.
Neden tek başına z-index yetmez
Geliştiriciler, büyük bir z-index bir menüyü sayfanın geri kalanının üstüne koymakta başarısız olduktan sonra çoğu zaman bir portal'a başvurur. Nedeni katmanlama bağlamlarıdır (stacking context). position ve bir z-index'i olan, opacity'si 1'in altında olan, bir transform, bir filter ya da isolation: isolate içeren bir öğe yeni bir katmanlama bağlamı başlatır ve çocuklarının z-index değerleri yalnızca onun içinde birbiriyle yarışır. z-index: 1 olan bir kartın içindeki z-index: 9999'lu bir çocuk, z-index: 2 olan kardeş bir kartın yine altında durur.
Bir portal öğeyi bu tür her atadan çıkarır. <body>'nin bir çocuğu olarak z-index'i sayfanın en üst düzey öğeleriyle karşılaştırılır, bu yüzden katmanlar için 1000 gibi mütevazı bir değer yeterlidir.
Yaygın hatalar
Henüz var olmayan bir düğüme portal yapmak. Öğe eksikse document.getElementById('modal-root') null döndürür ve createPortal "Target container is not a DOM element" hatası fırlatır. HTML'i kontrol et ya da document.body'ye portal yap.
Hedef düğümü render sırasında oluşturmak. Bileşen gövdesine document.createElement('div') yazmak her render'da yeni bir düğüm oluşturur. Onu bir efektte bir kez oluştur ya da sabit bir kapsayıcı kullan.
Tetikleyicisini takip etmeyen açılır pencereler. getBoundingClientRect()'ten konumlandırılan bir tooltip, sayfa kaydırıldığında ya da yeniden boyutlandırıldığında tetikleyicisini takip etmez. scroll ve resize'da yeniden hesapla (ve bu dinleyicileri efektin temizlemesinde kaldır) ya da sayfa kaydırıldığında tooltip'i kapat.
Sıkça Sorulan Sorular
React'te portal nedir?
Çocukları ebeveyn bileşenin DOM öğesinin dışındaki bir DOM düğümüne render etmenin bir yoludur. react-dom'dan createPortal(children, domNode) ile bir tane oluşturursun. Çocuklar React ağacındaki yerlerini korur, bu yüzden props, state ve context her zamanki gibi çalışır.
Ne zaman portal kullanmalıyım?
Bir şeyin kapsayıcısının üstünde ya da dışında görünmesi gerektiğinde: modallar, tooltip'ler, açılır menüler, bildirimler. overflow: hidden'lı, transform'lu ya da kendi katmanlama bağlamı olan bir ebeveyn aksi hâlde onu kırpar ya da gizler.
Olaylar bir portal'ın dışına kabarcıklanır mı?
Evet, React ağacı boyunca. Bir portal içindeki tıklama, DOM düğümü document.body içinde yaşasa bile React ebeveynlerindeki onClick işleyicilerine ulaşır. addEventListener ile eklenen yerel dinleyiciler ise DOM ağacını izler.
Context bir portal içinde çalışır mı?
Evet. Context, olaylar gibi, React ağacını izler. document.body içine render edilen bir modal, onu oluşturan bileşenin üstündeki provider'lardan temayı ya da kullanıcıyı yine okur.
Bir modal için portal'a ihtiyacım var mı?
Her zaman değil. showModal() ile açılan yerel <dialog> öğesi tarayıcının üst katmanında, her z-index'in üstünde, yerleşik odak yönetimiyle çizilir. Portal, özel modallar ile tooltip'ler ve menüler için olağan seçimdir.