Menu

React Portal'ları: Modal ve Tooltip için createPortal

createPortal bir bileşenin bir kısmını document.body gibi farklı bir DOM düğümüne render eder; bileşen ise React ağacında aynı yerde kalır. Onu overflow hidden ve z-index katmanlamasından kaçması gereken modallar, tooltip'ler ve menüler için kullan.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

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?)
  • children herhangi bir JSX'tir: bir öğe, bir fragment, bir bileşen.
  • domNode, document.body ya da document.getElementById('modal-root') gibi mevcut bir DOM öğesidir. Portal render olduğunda var olmalıdır.
  • key isteğ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" ve aria-modal="true", başlığını gösteren aria-labelledby ile.
  • 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.

Coddy programlama dilleri çizimi

Coddy ile kodlamayı öğren

BAŞLA