useLayoutEffect
useLayoutEffect
, tarayıcı bileşeni ekrana çizmeden önce tetiklenen bir useEffect
çeşididir.
useLayoutEffect(setup, dependencies?)
Referans
useLayoutEffect(setup, dependencies?)
Yerleşim (layout) ölçümlerini gerçekleştirmek için, useLayoutEffect
’i tarayıcının ekrana yeniden çizme işleminden (repainting) önce çağırın:
import { useState, useRef, useLayoutEffect } from 'react';
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
// ...
Daha fazla örnek için aşağıya bakın.
Parametreler
-
kurulum (setup)
: Efekt kodunu barındıran fonksiyondur. Kurulum fonksiyonunuz ayrıca isteğe bağlı temizleme (cleanup) fonksiyonu döndürebilir. React, bileşeninizi DOM’a eklemeden önce kurulum fonksiyonunuzu çalıştırır. Bağımlılıkların değişmesiyle tetiklenen render’ların ardından öncelikle eski değerlerle birlikte temizleme fonksiyonunuzu (varsa) çalıştırılır ve ardından yeni değerlerle birlikte kurulum fonksiyonuzu çalıştırılır. Bileşen DOM’dan kaldırılmadan önce, React son kez temizleme fonksiyonunuzu çalıştırır. -
isteğe bağlı
bağımlılıklar (dependencies)
: Kurulum fonksiyonu içerisinde başvurulan reaktif değerlerin listesidir. Reaktif değerler; prop’lar, state’ler ve de bileşenin içerisinde tanımlanan değişkenler ve fonksiyonlardır. Linter’ınız React için yapılandırılmışsa, her reaktif değerin doğru bir şekilde bağımlılıklara eklendiğini kontrol eder. Bağımlılık listesi, sabit sayıda öğeye sahiptir ve[dep1, dep2, dep3]
şeklinde satır içi yazılmalıdır. React,Object.is
kullanarak bağımlılıkları önceki değerleriyle karşılaştırır. Eğer bu argümanı atlarsanız, bileşen her render edildiğinde efekt çalıştırılır.
Dönüş değeri
useLayoutEffect
, undefined
döndürür.
Dikkat edilmesi gerekenler
-
useLayoutEffect
bir Hook olduğundan, yalnızca bileşeninizin en üst kapsamında ya da kendi Hook’larınızda çağırabilirsiniz. Döngülerin ve koşulların içinde çağıramazsınız. Eğer çağırmak zorunda kaldıysanız yeni bir bileşene çıkarın ve efekti oraya taşıyın. -
Katı Mod (Strict Mode) açık olduğunda React, ilk kurulumdan önce yalnızca geliştirme ortamında olmak üzere ekstra bir kurulum+temizleme döngüsü çalıştırır. Bu, temizleme mantığının kurulum mantığını “yansıtmasını” ve kurulumun yaptığı her şeyi durdurmasını veya geri almasını sağlayan bir stres testidir. Eğer bir probleme neden olursa temizleme fonksiyonunu implemente edin.
-
Bileşen içerisinde tanımlanıp bağımlılıklarınıza eklenmiş bazı nesneler ve fonksiyonlar, efektin gereksiz yere yeniden çalışmasına neden olabilir. Bu sorunu çözmek için gereksiz nesne ve fonksiyon bağımlılıklarını kaldırabilirsiniz. Ayrıca state güncellemelerini ve reaktif olmayan kodları efekt dışına çıkarabilirsiniz.
-
Efektler yalnızca istemcide (client) çalışır. Sunucu taraflı renderlama (server-side rendering) esnasında çalışmaz.
-
useLayoutEffect
fonksiyonunun kodu ve içerisinde planlanan state güncellemeleri, tarayıcının ekrana yeniden çizme işlemini bloklar. Aynı zamanda gereğinden fazla kullanılması durumunda uygulamanızı yavaşlatabilir. Mümkün olduğuncauseEffect
’i tercih edin. -
If you trigger a state update inside
useLayoutEffect
, React will execute all remaining Effects immediately includinguseEffect
.
Kullanım
Tarayıcı ekrana çizmeden önce yerleşimi ölçmek
Çoğu bileşen, render edeceği elementlere karar vermek için ekrandaki konumlarını ve boyutlarını bilmeye ihtiyaç duymaz. Sadece JSX döndürürler. Sonrasında tarayıcı, yerleşimlerini (konum ve boyut) hesaplar ve ekrana çizer.
Bazen bu yeterli olmaz. Bir elementin üzerine gelindiğinde yanında görünen bir ipucu kutusunu (tooltip) düşünün. Yeterli alan mevcutsa öğenin üstünde, yoksa altında görünmelidir. Doğru konumda render edilmesi için kutunun yüksekliğini bilmeye ihtiyacınız (üstteki boşluğa sığdığından emin olmak adına) vardır.
Bunu yapmak için çift dikiş renderlamaya ihtiyacınız vardır:
- İpucunu herhangi bir yerde render edin (konumu farketmez).
- Yüksekliğini ölçün ve ipucunu yerleştireceğiniz yere karar verin.
- İpucunu doğru yerde yeniden render edin.
Tüm bunlar tarayıcı ekrana yeniden çizmeden önce yaşanmalıdır. Kullanıcının kutunun konum değiştirme hareketini görmesini istemezsiniz. Yerleşim ölçümlerini yapmak için useLayoutEffect
’i çağırın:
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // Henüz yüksekliğini bilmiyorsunuz
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // Gerçek yüksekliği öğrendikten sonra yeniden render edin
}, []);
// ...aşağıda bulunan rendering mantığında tooltipHeight'i kullanın...
}
İşleyişi adım adım şu şekilde açıklanabilir:
Tooltip
, başlangıçtatooltipHeight = 0
olarak render edilir (kutu yanlış konumlandırılabilir).- React, DOM’a yerleştirir ve
useLayoutEffect
’teki kodu çalıştırır. useLayoutEffect
, ipucu kutusunun yüksekliğini ölçer ve anında yeniden render’ı tetikler.Tooltip
, gerçektooltipHeight
ile yeniden render edilir (kutu doğru konumlandırılır).- React, DOM’u günceller ve tarayıcı ipucu kutusunu nihayet görüntüler.
Aşağıdaki düğmelerin üzerine gelip ipucu kutusunun sığıp sığmadığına bağlı olarak nasıl konumlandırılacağını görün:
import { useRef, useLayoutEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import TooltipContainer from './TooltipContainer.js'; export default function Tooltip({ children, targetRect }) { const ref = useRef(null); const [tooltipHeight, setTooltipHeight] = useState(0); useLayoutEffect(() => { const { height } = ref.current.getBoundingClientRect(); setTooltipHeight(height); console.log('Ölçülen ipucu kutusu yüksekliği: ' + height); }, []); let tooltipX = 0; let tooltipY = 0; if (targetRect !== null) { tooltipX = targetRect.left; tooltipY = targetRect.top - tooltipHeight; if (tooltipY < 0) { // Yukarıya sığmadığı için altına yerleştirilir tooltipY = targetRect.bottom; } } return createPortal( <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> {children} </TooltipContainer>, document.body ); }
Dikkat edin, Tooltip
bileşeni çift dikiş (önce tooltipHeight = 0
olarak, ardından ölçülen yükseklikle) render edilmesine rağmen, yalnızca nihai sonucu görürsünüz. Elbette bunun için örnekteki gibi useEffect
yerine useLayoutEffect
kullanmanız gerekir. Farkı detaylıca inceleyelim.
Örnek 1 / 2: useLayoutEffect
tarayıcının ekrana çizme işlemini bloklar
React; useLayoutEffect
kodunun ve içerisinde planlanan durum güncellemelerinin, tarayıcı ekranı yeniden çizmeden önce işleme alınacağının garantisini verir. Bu da kullanıcı ilk render’ı fark edemeden tooltip’i ölçmenize ve tekrar render etmenize olanak tanır. Başka bir deyişle, useLayoutEffect
tarayıcının ekrana çizmesini engeller.
import { useRef, useLayoutEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import TooltipContainer from './TooltipContainer.js'; export default function Tooltip({ children, targetRect }) { const ref = useRef(null); const [tooltipHeight, setTooltipHeight] = useState(0); useLayoutEffect(() => { const { height } = ref.current.getBoundingClientRect(); setTooltipHeight(height); }, []); let tooltipX = 0; let tooltipY = 0; if (targetRect !== null) { tooltipX = targetRect.left; tooltipY = targetRect.top - tooltipHeight; if (tooltipY < 0) { // Yukarıya sığmadığı için altına yerleştirilir tooltipY = targetRect.bottom; } } return createPortal( <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> {children} </TooltipContainer>, document.body ); }
Sorun giderme
“useLayoutEffect
does nothing on the server” hatası alıyorum
useLayoutEffect
’in amacı, bileşeninizin render anında yerleşim bilgisini kullanmasına izin vermektir:
- Başlangıç içeriğini render edin.
- Tarayıcı ekranı çizmeden önce yerleşimi ölçün.
- Okuduğunuz yerleşim bilgisini kullanarak nihai içeriği render edin.
Siz veya framework’ünüz sunucu taraflı render’lama kullanıyorsa, React uygulamanız ilk render için sunucuda HTML render eder. Bu da JavaScript kodu yüklenmeden önce HTML’ini göstermenize sebep olur.
Sorunun kaynağı sunucuda yerleşim (layout) bilgisinin bulunmamasıdır.
Önceki örneklerde bahsedilen Tooltip
bileşenindeki useLayoutEffect
çağrısı, içeriğin yüksekliğine bağlı olarak doğru konuma yerleşmesine izin veriyordu (içeriğin üstünde veya altında). Tooltip
’i sunucu HTML’inin bir parçası olarak render etmeye çalışırsanız, konumu belirlemek imkansız olur. Sunucuda yerleşim yoktur! Bu nedenle, sunucuda render edilen bileşeniniz JavaScript yüklenip çalıştırıldıktan sonra nihai konumuna “zıplar”.
Genellikle yerleşim bilgisine ihtiyaç duyan bileşenlerin sunucuda render edilmesi gereksizdir. Örneğin, ilk render’da Tooltip
göstermek çoğu zaman mantıklı değildir. İstemci etkileşimiyle tetiklenmelidir.
Bununla birlikte bu sorunla karşılaştığınızda seçebileceğiniz birkaç farklı seçenek vardır:
-
useLayoutEffect
’iuseEffect
ile değiştirin. Bu, React’a ekrana çizme işlemini bloke etmesine gerek olmadan ilk render sonucunu görüntüleyebileceğini söyler (çünkü efektiniz çalışmadan önce HTML render edilmiş olacaktır). -
Alternatif olarak, bileşeninizi yalnızca istemci taraflı render olacak şekilde işaretleyin. Böylece bileşen sunucu tarafında render edilirken en yakındaki
<Suspense>
sınırına (boundary) yüklenme fallback’i olarak verilen bileşen (örneğin, spinner veya glimmer) ile değiştirilir. -
Alternatif olarak,
useLayoutEffect
kullanan bileşeni hidratlama sonrasında render ettirebilirsiniz. Başlangıç değerifalse
olanisMounted
isminde bir state oluşturun veuseEffect
içerisindetrue
olarak ayarlayın. Render ederken lojiğiniz şöyle olabilir:return isMounted ? <RealContent /> : <FallbackContent />
. Sunucudayken veya hidratlama sırasında, kullanıcıuseLayoutEffect
çağırılmayanFallbackContent
’i görür. İstemci tarafında React, içeriğiRealContent
ile değiştirir. -
Bileşeninizi harici bir veri deposu (data store) ile senkron tutuyorsanız ve
useEffect
’i yerleşimi ölçmek yerine başka sebeplerle kullanıyorsanız, bunun yerine sunucu taraflı render’ı destekleyenuseSyncExternalStore
’u kullanmayı düşünün.