Gereksiz yeniden oluşturmaları önleyerek React uygulamalarını optimize etmek için kapsamlı bir rehber. Gelişmiş performans için memoization, PureComponent ve daha fazlasını öğrenin.
React Render Optimizasyonu: Gereksiz Yeniden Oluşturmaların Önlenmesinde Uzmanlaşma
React, kullanıcı arayüzleri oluşturmak için güçlü bir JavaScript kütüphanesi olup, bazen aşırı veya gereksiz yeniden oluşturmalar (re-render) nedeniyle performans darboğazları yaşayabilir. Birçok bileşene sahip karmaşık uygulamalarda, bu yeniden oluşturmalar performansı önemli ölçüde düşürerek yavaş bir kullanıcı deneyimine yol açabilir. Bu kılavuz, React'te gereksiz yeniden oluşturmaları önlemek için kapsamlı bir teknik genel bakış sunarak, uygulamalarınızın dünya çapındaki kullanıcılar için hızlı, verimli ve duyarlı olmasını sağlar.
React'te Yeniden Oluşturmaları Anlamak
Optimizasyon tekniklerine dalmadan önce, React'in render sürecinin nasıl çalıştığını anlamak çok önemlidir. Bir bileşenin state'i veya prop'ları değiştiğinde, React o bileşenin ve alt bileşenlerinin yeniden oluşturulmasını tetikler. Bu süreç, sanal DOM'u güncellemeyi ve gerçek DOM'a uygulanacak minimum değişiklik setini belirlemek için önceki sürümle karşılaştırmayı içerir.
Ancak, her state veya prop değişikliği bir DOM güncellemesi gerektirmez. Eğer yeni sanal DOM, bir öncekiyle aynıysa, yeniden oluşturma esasen kaynak israfıdır. Bu gereksiz yeniden oluşturmalar değerli CPU döngülerini tüketir ve özellikle karmaşık bileşen ağaçlarına sahip uygulamalarda performans sorunlarına yol açabilir.
Gereksiz Yeniden Oluşturmaları Tespit Etme
Yeniden oluşturmaları optimize etmenin ilk adımı, bunların nerede meydana geldiğini tespit etmektir. React, bu konuda size yardımcı olacak birkaç araç sunar:
1. React Profiler
Chrome ve Firefox için React DevTools eklentisinde bulunan React Profiler, React bileşenlerinizin performansını kaydetmenize ve analiz etmenize olanak tanır. Hangi bileşenlerin yeniden oluşturulduğu, render edilmelerinin ne kadar sürdüğü ve neden yeniden oluşturuldukları hakkında bilgiler sağlar.
Profiler'ı kullanmak için, DevTools'ta "Record" düğmesini etkinleştirin ve uygulamanızla etkileşime geçin. Kayıttan sonra, Profiler bileşen ağacını ve render sürelerini görselleştiren bir alev grafiği (flame chart) gösterecektir. Render edilmesi uzun süren veya sık sık yeniden oluşturulan bileşenler, optimizasyon için başlıca adaylardır.
2. Why Did You Render?
"Why Did You Render?", React'i yamalayarak, yeniden oluşturmaya neden olan belirli prop'ları konsola yazdırarak sizi potansiyel olarak gereksiz yeniden oluşturmalar hakkında bilgilendiren bir kütüphanedir. Bu, yeniden oluşturma sorunlarının temel nedenini belirlemede son derece yardımcı olabilir.
"Why Did You Render?" kütüphanesini kullanmak için, onu bir geliştirme bağımlılığı olarak yükleyin:
npm install @welldone-software/why-did-you-render --save-dev
Daha sonra, uygulamanızın giriş noktasına (örneğin, index.js) import edin:
import whyDidYouRender from '@welldone-software/why-did-you-render';
if (process.env.NODE_ENV === 'development') {
whyDidYouRender(React, {
include: [/.*/]
});
}
Bu kod, geliştirme modunda "Why Did You Render?" kütüphanesini etkinleştirecek ve potansiyel olarak gereksiz yeniden oluşturmalar hakkında bilgileri konsola yazdıracaktır.
3. Console.log İfadeleri
Basit ama etkili bir teknik, ne zaman yeniden oluşturulduğunu izlemek için bileşeninizin render
metodu (veya fonksiyonel bileşen gövdesi) içine console.log
ifadeleri eklemektir. Profiler veya "Why Did You Render?" kadar gelişmiş olmasa da, bu, beklenenden daha sık yeniden oluşturulan bileşenleri hızla vurgulayabilir.
Gereksiz Yeniden Oluşturmaları Önleme Teknikleri
Performans sorunlarına neden olan bileşenleri belirledikten sonra, gereksiz yeniden oluşturmaları önlemek için çeşitli teknikler kullanabilirsiniz:
1. Memoization
Memoization, pahalı fonksiyon çağrılarının sonuçlarını önbelleğe almayı ve aynı girdiler tekrar oluştuğunda önbelleğe alınmış sonucu döndürmeyi içeren güçlü bir optimizasyon tekniğidir. React'te memoization, prop'ları değişmediyse bileşenlerin yeniden oluşturulmasını önlemek için kullanılabilir.
a. React.memo
React.memo
, bir fonksiyonel bileşeni memoize eden bir yüksek mertebeden bileşendir (higher-order component). Mevcut prop'ları önceki prop'larla yüzeysel olarak karşılaştırır ve sadece prop'lar değiştiyse bileşeni yeniden oluşturur.
Örnek:
const MyComponent = React.memo(function MyComponent(props) {
return <div>{props.data}</div>;
});
Varsayılan olarak, React.memo
tüm prop'ların yüzeysel bir karşılaştırmasını yapar. Karşılaştırma mantığını özelleştirmek için React.memo
'ya ikinci bir argüman olarak özel bir karşılaştırma fonksiyonu sağlayabilirsiniz.
const MyComponent = React.memo(function MyComponent(props) {
return <div>{props.data}</div>;
}, (prevProps, nextProps) => {
// Prop'lar eşitse true, farklıysa false döndürün
return prevProps.data === nextProps.data;
});
b. useMemo
useMemo
, bir hesaplamanın sonucunu memoize eden bir React hook'udur. Bir fonksiyon ve bir bağımlılık dizisi alır. Fonksiyon sadece bağımlılıklardan biri değiştiğinde yeniden çalıştırılır ve sonraki render'larda memoize edilmiş sonuç döndürülür.
useMemo
, özellikle pahalı hesaplamaları memoize etmek veya alt bileşenlere prop olarak geçirilen nesnelere veya fonksiyonlara kararlı referanslar oluşturmak için kullanışlıdır.
Örnek:
const memoizedValue = useMemo(() => {
// Burada pahalı bir hesaplama yapın
return computeExpensiveValue(a, b);
}, [a, b]);
2. PureComponent
PureComponent
, shouldComponentUpdate
metodunda prop'ların ve state'in yüzeysel bir karşılaştırmasını uygulayan React bileşenleri için bir temel sınıftır. Prop'lar ve state değişmediyse, bileşen yeniden oluşturulmaz.
PureComponent
, render işlemi için yalnızca prop'larına ve state'ine bağlı olan ve context veya diğer dış faktörlere dayanmayan bileşenler için iyi bir seçimdir.
Örnek:
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.data}</div>;
}
}
Önemli Not: PureComponent
ve React.memo
yüzeysel karşılaştırmalar yapar. Bu, nesnelerin ve dizilerin içeriklerini değil, yalnızca referanslarını karşılaştırdıkları anlamına gelir. Prop'larınız veya state'iniz iç içe nesneler veya diziler içeriyorsa, değişikliklerin doğru bir şekilde algılandığından emin olmak için değişmezlik (immutability) gibi teknikler kullanmanız gerekebilir.
3. shouldComponentUpdate
shouldComponentUpdate
yaşam döngüsü metodu, bir bileşenin yeniden oluşturulup oluşturulmayacağını manuel olarak kontrol etmenize olanak tanır. Bu metod, argüman olarak bir sonraki prop'ları ve bir sonraki state'i alır ve bileşenin yeniden oluşturulması gerekiyorsa true
, gerekmiyorsa false
döndürmelidir.
shouldComponentUpdate
, yeniden oluşturma üzerinde en fazla kontrolü sağlarken, aynı zamanda en fazla manuel çabayı gerektirir. Bir yeniden oluşturmanın gerekli olup olmadığını belirlemek için ilgili prop'ları ve state'i dikkatlice karşılaştırmanız gerekir.
Örnek:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Prop'ları ve state'i burada karşılaştırın
return nextProps.data !== this.props.data || nextState.count !== this.state.count;
}
render() {
return <div>{this.props.data}</div>;
}
}
Dikkat: shouldComponentUpdate
'i yanlış uygulamak beklenmedik davranışlara ve hatalara yol açabilir. Karşılaştırma mantığınızın kapsamlı olduğundan ve ilgili tüm faktörleri hesaba kattığından emin olun.
4. useCallback
useCallback
, bir fonksiyon tanımını memoize eden bir React hook'udur. Bir fonksiyon ve bir bağımlılık dizisi alır. Fonksiyon sadece bağımlılıklardan biri değiştiğinde yeniden tanımlanır ve sonraki render'larda memoize edilmiş fonksiyon döndürülür.
useCallback
, özellikle React.memo
veya PureComponent
kullanan alt bileşenlere prop olarak fonksiyonlar geçerken kullanışlıdır. Fonksiyonu memoize ederek, üst bileşen yeniden oluşturulduğunda alt bileşenin gereksiz yere yeniden oluşturulmasını önleyebilirsiniz.
Örnek:
const handleClick = useCallback(() => {
// Tıklama olayını işle
console.log('Clicked!');
}, []);
5. Değişmezlik (Immutability)
Değişmezlik (Immutability), veriyi oluşturulduktan sonra değiştirilemez, yani sabit olarak ele almayı içeren bir programlama konseptidir. Değişmez verilerle çalışırken, herhangi bir değişiklik mevcut olanı değiştirmek yerine yeni bir veri yapısının oluşturulmasıyla sonuçlanır.
Değişmezlik, React yeniden oluşturmalarını optimize etmek için çok önemlidir çünkü React'in prop'lardaki ve state'teki değişiklikleri yüzeysel karşılaştırmalar kullanarak kolayca tespit etmesini sağlar. Bir nesneyi veya diziyi doğrudan değiştirirseniz, nesneye veya diziye olan referans aynı kaldığı için React değişikliği algılayamaz.
React'te değişmez verilerle çalışmak için Immutable.js veya Immer gibi kütüphaneleri kullanabilirsiniz. Bu kütüphaneler, değişmez verileri oluşturmayı ve işlemeyi kolaylaştıran veri yapıları ve fonksiyonlar sağlar.
Immer kullanarak örnek:
import { useImmer } from 'use-immer';
function MyComponent() {
const [data, setData] = useImmer({
name: 'John',
age: 30
});
const updateName = () => {
setData(draft => {
draft.name = 'Jane';
});
};
return (
<div>
<p>Name: {data.name}</p>
<button onClick={updateName}>Update Name</button>
</div>
);
}
6. Kod Bölme (Code Splitting) ve Tembel Yükleme (Lazy Loading)
Kod bölme, uygulamanızın kodunu isteğe bağlı olarak yüklenebilen daha küçük parçalara ayırmayı içeren bir tekniktir. Bu, uygulamanızın ilk yükleme süresini önemli ölçüde iyileştirebilir, çünkü tarayıcının yalnızca mevcut görünüm için gerekli olan kodu indirmesi gerekir.
React, React.lazy
fonksiyonu ve Suspense
bileşeni kullanarak kod bölme için yerleşik destek sağlar. React.lazy
, bileşenleri dinamik olarak import etmenize olanak tanırken, Suspense
bileşen yüklenirken bir yedek kullanıcı arayüzü görüntülemenizi sağlar.
Örnek:
import React, { Suspense } from 'react';
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
);
}
7. Anahtarları (Keys) Verimli Kullanma
React'te eleman listelerini render ederken, her elemana benzersiz anahtarlar (key) sağlamak çok önemlidir. Anahtarlar, React'in hangi elemanların değiştiğini, eklendiğini veya kaldırıldığını belirlemesine yardımcı olarak DOM'u verimli bir şekilde güncellemesini sağlar.
Dizideki elemanların sırası değiştiğinde değişebilecekleri ve gereksiz yeniden oluşturmalara yol açabilecekleri için dizi indekslerini anahtar olarak kullanmaktan kaçının. Bunun yerine, her eleman için bir veritabanından gelen ID veya oluşturulmuş bir UUID gibi benzersiz bir tanımlayıcı kullanın.
8. Context Kullanımını Optimize Etme
React Context, bileşen ağacının her seviyesinden açıkça prop geçirmeden bileşenler arasında veri paylaşmanın bir yolunu sunar. Ancak, Context'in aşırı kullanımı performans sorunlarına yol açabilir, çünkü bir Context'i tüketen herhangi bir bileşen, Context değeri her değiştiğinde yeniden oluşturulur.
Context kullanımını optimize etmek için şu stratejileri göz önünde bulundurun:
- Birden çok, daha küçük Context kullanın: Tüm uygulama verilerini depolamak için tek ve büyük bir Context kullanmak yerine, onu daha küçük, daha odaklı Context'lere ayırın. Bu, belirli bir Context değeri değiştiğinde yeniden oluşturulan bileşen sayısını azaltacaktır.
- Context değerlerini memoize edin: Context sağlayıcısı tarafından sağlanan değerleri memoize etmek için
useMemo
kullanın. Bu, değerler aslında değişmediyse Context tüketicilerinin gereksiz yere yeniden oluşturulmasını önleyecektir. - Context'e alternatifleri düşünün: Bazı durumlarda, Redux veya Zustand gibi diğer state yönetim çözümleri, özellikle çok sayıda bileşeni olan ve sık sık state güncellemeleri yapılan karmaşık uygulamalar için Context'ten daha uygun olabilir.
Uluslararası Hususlar
React uygulamalarını küresel bir kitle için optimize ederken, aşağıdaki faktörleri göz önünde bulundurmak önemlidir:
- Değişen ağ hızları: Farklı bölgelerdeki kullanıcıların ağ hızları büyük ölçüde farklılık gösterebilir. Uygulamanızı, ağ üzerinden indirilmesi ve aktarılması gereken veri miktarını en aza indirecek şekilde optimize edin. Görüntü optimizasyonu, kod bölme ve tembel yükleme gibi teknikleri kullanmayı düşünün.
- Cihaz yetenekleri: Kullanıcılar uygulamanıza yüksek kaliteli akıllı telefonlardan daha eski, daha az güçlü cihazlara kadar çeşitli cihazlardan erişiyor olabilir. Uygulamanızı çeşitli cihazlarda iyi performans gösterecek şekilde optimize edin. Duyarlı tasarım, uyarlanabilir görüntüler ve performans profili oluşturma gibi teknikleri kullanmayı düşünün.
- Yerelleştirme: Uygulamanız birden çok dil için yerelleştirilmişse, yerelleştirme sürecinin performans darboğazları yaratmadığından emin olun. Verimli yerelleştirme kütüphaneleri kullanın ve metin dizilerini doğrudan bileşenlerinize sabit olarak kodlamaktan kaçının.
Gerçek Dünya Örnekleri
Bu optimizasyon tekniklerinin nasıl uygulanabileceğine dair birkaç gerçek dünya örneğini ele alalım:
1. E-ticaret Ürün Listeleme
Yüzlerce ürünü görüntüleyen bir ürün listeleme sayfasına sahip bir e-ticaret web sitesi düşünün. Her ürün öğesi ayrı bir bileşen olarak render edilir.
Optimizasyon olmadan, kullanıcı ürün listesini her filtrelediğinde veya sıraladığında, tüm ürün bileşenleri yeniden oluşturulur ve bu da yavaş ve takılan bir deneyime yol açar. Bunu optimize etmek için, ürün bileşenlerini memoize etmek üzere React.memo
kullanabilir ve böylece yalnızca prop'ları (ör. ürün adı, fiyat, resim) değiştiğinde yeniden oluşturulmalarını sağlayabilirsiniz.
2. Sosyal Medya Akışı
Bir sosyal medya akışı genellikle her biri yorumlar, beğeniler ve diğer etkileşimli öğeler içeren bir gönderi listesi görüntüler. Bir kullanıcı bir gönderiyi her beğendiğinde veya bir yorum eklediğinde tüm akışı yeniden oluşturmak verimsiz olurdu.
Bunu optimize etmek için, gönderileri beğenme ve yorum yapma olay işleyicilerini (event handler) memoize etmek üzere useCallback
kullanabilirsiniz. Bu, bu olay işleyicileri tetiklendiğinde gönderi bileşenlerinin gereksiz yere yeniden oluşturulmasını önleyecektir.
3. Veri Görselleştirme Paneli
Bir veri görselleştirme paneli genellikle yeni verilerle sık sık güncellenen karmaşık çizelgeler ve grafikler görüntüler. Veriler her değiştiğinde bu çizelgeleri yeniden oluşturmak hesaplama açısından pahalı olabilir.
Bunu optimize etmek için, grafik verilerini memoize etmek ve grafikleri yalnızca memoize edilmiş veriler değiştiğinde yeniden oluşturmak için useMemo
kullanabilirsiniz. Bu, yeniden oluşturma sayısını önemli ölçüde azaltacak ve panelin genel performansını artıracaktır.
En İyi Uygulamalar
React yeniden oluşturmalarını optimize ederken akılda tutulması gereken bazı en iyi uygulamalar şunlardır:
- Uygulamanızın profilini çıkarın: Performans sorunlarına neden olan bileşenleri belirlemek için React Profiler veya "Why Did You Render?" kullanın.
- En kolay hedeflerle başlayın: En sık yeniden oluşturulan veya render edilmesi en uzun süren bileşenleri optimize etmeye odaklanın.
- Memoization'ı akıllıca kullanın: Her bileşeni memoize etmeyin, çünkü memoization'ın kendisinin de bir maliyeti vardır. Yalnızca gerçekten performans sorunlarına neden olan bileşenleri memoize edin.
- Değişmezlik kullanın: React'in prop'lardaki ve state'teki değişiklikleri daha kolay algılamasını sağlamak için değişmez veri yapıları kullanın.
- Bileşenleri küçük ve odaklı tutun: Daha küçük, daha odaklı bileşenleri optimize etmek ve bakımını yapmak daha kolaydır.
- Optimizasyonlarınızı test edin: Optimizasyon tekniklerini uyguladıktan sonra, optimizasyonların istenen etkiyi yarattığından ve yeni hatalara yol açmadığından emin olmak için uygulamanızı kapsamlı bir şekilde test edin.
Sonuç
Gereksiz yeniden oluşturmaları önlemek, React uygulamalarının performansını optimize etmek için çok önemlidir. React'in render sürecinin nasıl çalıştığını anlayarak ve bu kılavuzda açıklanan teknikleri kullanarak, uygulamalarınızın yanıt verebilirliğini ve verimliliğini önemli ölçüde artırabilir, dünyanın dört bir yanındaki kullanıcılara daha iyi bir kullanıcı deneyimi sunabilirsiniz. Uygulamanızın profilini çıkarmayı, performans sorunlarına neden olan bileşenleri belirlemeyi ve bu sorunları gidermek için uygun optimizasyon tekniklerini uygulamayı unutmayın. Bu en iyi uygulamaları takip ederek, kod tabanınızın karmaşıklığına veya boyutuna bakılmaksızın React uygulamalarınızın hızlı, verimli ve ölçeklenebilir olmasını sağlayabilirsiniz.