Takip et

React’ta useState Fonksiyonel Güncellemeler: Performans ve Optimizasyon Sırları

React uygulamalarınızda yavaşlamalar mı yaşıyorsunuz? Özellikle karmaşık bileşenlerde veya büyük veri setleriyle çalışırken, useState kullanım şeklinizin uygulama performansını doğrudan etkilediğini biliyor muydunuz? Bu makale, React geliştiricilerinin sıkça gözden kaçırdığı bir performans ipucunu, yani useState(() => ...) ile yapılan fonksiyonel güncellemelerin neden useState({...}) şeklindeki doğrudan obje güncellemelerine göre çok daha verimli olduğunu detaylı bir şekilde açıklayacak. Hazır olun, React performansınızı bir sonraki seviyeye taşıyacak sırları keşfetmeye başlıyoruz!

Modern web uygulamaları, kullanıcı deneyimini merkeze alarak geliştirilir. Kullanıcılar, anında yanıt veren, akıcı ve hızlı yüklenen arayüzler beklerler. Yavaş çalışan bir uygulama, kullanıcı memnuniyetini düşürür, dönüşüm oranlarını olumsuz etkiler ve hatta uygulamanın tamamen terk edilmesine neden olabilir. Özellikle e-ticaret siteleri, sosyal medya platformları veya yoğun veri girişi gerektiren iş uygulamaları gibi alanlarda performans, başarının anahtarıdır. Bir uygulamanın performansı, sadece yükleme süresiyle sınırlı değildir; aynı zamanda etkileşimlere tepki verme hızı, animasyonların akıcılığı ve karmaşık işlemlerin sorunsuz bir şekilde yürütülmesi gibi faktörleri de kapsar. React gibi bileşen tabanlı bir kütüphane kullanırken, her bir bileşenin kendi içinde ne kadar verimli çalıştığı, tüm uygulamanın genel performansını doğrudan etkiler. Bu nedenle, geliştiricilerin en temelden başlayarak performans optimizasyon tekniklerini anlaması ve uygulaması hayati önem taşır. JavaScript motorunun nasıl çalıştığından, React’ın dahili mekanizmalarına kadar pek çok detayı bilmek, daha hızlı ve daha duyarlı uygulamalar geliştirmemizi sağlar. Özellikle state yönetimindeki küçük optimizasyonlar bile, büyük ölçekli uygulamalarda ciddi farklılıklar yaratabilir.

Peki, bu performans sorunları nerede ortaya çıkar? Genellikle, uygulamanın state’i güncellendiğinde React’ın bileşenleri yeniden render etmesi gerekir. Bu yeniden render işlemleri, genellikle beklediğimizden daha sık veya daha fazla maliyetli olabilir. Eğer bir bileşen her state güncellemesinde gereksiz yere karmaşık hesaplamalar yapıyor veya büyük objeleri tekrar tekrar oluşturuyorsa, bu durum performans düşüşlerine yol açar. Örneğin, bir liste elemanını filtrelerken, her tuş vuruşunda tüm listenin yeniden işlenmesi veya binlerce elemanlık bir objenin kopyalanması gibi durumlar, uygulamanın donmasına veya gecikmeler yaşanmasına neden olabilir. İşte bu noktada, React’ın bize sunduğu çeşitli optimizasyon araçları ve teknikleri devreye girer. Bunlar arasında memo, useCallback, useMemo gibi kancalar ve elbette bu makalenin odak noktası olan useState kancasının özel bir kullanım biçimi yer alır. Bu teknikler, React’ın sadece gerçekten değişen kısımları yeniden render etmesini sağlayarak ve gereksiz maliyetli işlemleri engelleyerek uygulamanın daha verimli çalışmasına yardımcı olur. Sonuç olarak, performans optimizasyonu sadece teknik bir gereklilik değil, aynı zamanda kullanıcılarınıza mümkün olan en iyi deneyimi sunmak için bir taahhüttür.

Temel Kavramlar: React’ta State Yönetimi ve useState Hook’u Nasıl Çalışır?

React, dinamik kullanıcı arayüzleri oluşturmak için bir kütüphanedir ve bu dinamizmi sağlamanın temel yolu “state” kavramıdır. State, bir bileşenin zaman içinde değişebilen ve bu değişikliklere göre arayüzün yeniden çizilmesini tetikleyen veri koleksiyonudur. Örneğin, bir sayfa üzerindeki bir metin kutusunun değeri, bir açılır menünün açık veya kapalı olma durumu ya da bir kullanıcının sepetindeki ürün sayısı gibi bilgiler state içinde tutulur. React, bu state değişimlerini algılar ve ilgili bileşenleri (veya alt bileşenlerini) otomatik olarak yeniden render ederek arayüzü günceller. Bu sayede, geliştiriciler DOM manipülasyonu gibi karmaşık işlerle uğraşmak yerine, sadece state’i güncelleyerek kullanıcı arayüzünün doğru bir şekilde yansımasını sağlayabilirler.

Modern React uygulamalarında state yönetimi için en yaygın kullanılan yöntemlerden biri, “Hooks” adı verilen özel fonksiyonlardır. Hooks, fonksiyonel bileşenlerde state ve diğer React özelliklerini kullanmamızı sağlar. Bunlar arasında en temel ve en sık kullanılan hook ise useState‘tir. useState hook’u, bir fonksiyonel bileşende bir state değişkeni tanımlamanızı ve bu değişkeni güncellemenizi sağlar. Kullanımı oldukça basittir:


import React, { useState } from 'react';

function Sayac() {
  const [sayacDegeri, setSayacDegeri] = useState(0); // Başlangıç değeri 0

  return (
    

Sayaç: {sayacDegeri}

); }

Yukarıdaki örnekte, useState(0) çağrısı bize bir dizi döndürür. Bu dizinin ilk elemanı (sayacDegeri), mevcut state değeridir. İkinci elemanı (setSayacDegeri) ise bu state değerini güncellemek için kullanılan bir fonksiyondur. setSayacDegeri fonksiyonu çağrıldığında, React bileşeni yeniden render eder ve sayacDegeri yeni değeriyle güncellenmiş olur. Ancak burada dikkat edilmesi gereken önemli bir nokta var: useState'e verdiğimiz başlangıç değeri veya güncelleyici fonksiyonun içine verdiğimiz değer, React'ın nasıl davrandığını ciddi şekilde etkileyebilir.

Bir state değişkenini useState ile tanımlarken, başlangıç değeri tek seferlik olarak bileşenin ilk render'ında kullanılır. Sonraki render'larda, useState'in ilk argümanı göz ardı edilir ve React, dahili olarak tuttuğu state değerini döndürür. Ancak, state'i güncellemek için setSayacDegeri gibi bir setter fonksiyonu kullandığımızda, bu fonksiyona doğrudan yeni bir değer verebiliriz (örneğin, setSayacDegeri(5)). Veya, mevcut state'e bağımlı bir güncelleme yapmamız gerektiğinde, bir önceki state'i parametre olarak alan bir fonksiyon da verebiliriz (örneğin, setSayacDegeri(prevSayac => prevSayac + 1)). İşte bu ikinci yöntem, yani "fonksiyonel güncelleme", performans açısından kritik bir avantaj sunar ve makalemizin temelini oluşturur. Bu mekanizmanın derinliklerine inmeden önce, direkt obje kullanımıyla ortaya çıkan potansiyel performans maliyetlerini anlamak, konuyu daha iyi kavramamıza yardımcı olacaktır.

Problem Odaklı Bakış: useState({...}) Kullanımının Gizli Performans Maliyeti Nedir?

useState hook'unu kullanırken, başlangıç değerini doğrudan bir obje olarak vermek veya state'i güncellerken doğrudan bir obje göndermek oldukça yaygın bir yaklaşımdır. Örneğin, bir kullanıcının form bilgilerini tutmak için aşağıdaki gibi bir kullanım düşünebiliriz:


import React, { useState } from 'react';

function KullaniciProfilFormu() {
  const [profilBilgileri, setProfilBilgileri] = useState({
    ad: 'Ayşe',
    soyad: 'Yılmaz',
    yas: 30,
    eposta: 'ayse.yilmaz@example.com'
  });

  const handleInputChange = (e) => {
    setProfilBilgileri({
      ...profilBilgileri,
      [e.target.name]: e.target.value,
    });
  };

  return (
    
); }

Bu kod ilk bakışta tamamen doğru ve işlevsel görünüyor. Ancak, özellikle state objesi karmaşıklaştığında veya uygulamanın daha fazla performans gerektirdiği durumlarda, burada gizli bir performans maliyeti yatmaktadır. Bu maliyetin temelinde "eager initialization" (hevesli başlatma) ve "gereksiz obje yeniden oluşturma" kavramları yatar. Ne yazık ki, bu durum çoğu zaman gözden kaçar ve uygulamanızın farkında olmadan yavaşlamasına neden olabilir.

Peki, bu maliyet tam olarak nasıl ortaya çıkar? useState({...}) şeklinde bir obje literal'i kullanarak başlangıç değeri atadığınızda veya setter fonksiyonuna doğrudan yeni bir obje literal'i (setProfilBilgileri({...profilBilgileri, ...})) geçtiğinizde, her bileşen yeniden render edildiğinde bu obje *yeniden oluşturulur*. React, bir bileşeni ilk kez render ederken useState'e sağladığınız başlangıç değerini kullanır. Ancak, sonraki her render'da, eğer useState'e yine bir obje literal'i verirseniz, JavaScript motoru bu objeyi yeniden yaratır. Bu durum, özellikle objenin içeriği büyük ve karmaşık olduğunda, performansı olumsuz etkileyebilir. Neden mi? Çünkü React, bileşenlerin yeniden render edilip edilmeyeceğine karar verirken state ve prop'lardaki referans değişikliklerini kontrol eder. Eğer her render'da aynı içeriğe sahip olsa bile yeni bir obje referansı oluşturuluyorsa, React bunu bir değişiklik olarak algılayabilir ve ilgili bileşenleri (ve hatta bazen alt bileşenleri) gereksiz yere yeniden render etme eğiliminde olabilir.

Daha da önemlisi, useState'in ilk render dışındaki kullanımlarında da bu problem devam eder. Diyelim ki, bir state objesinin başlangıç değeri, maliyetli bir hesaplama sonucunda üretilmesi gereken büyük bir veri yapısı. Eğer bu değeri useState(expensiveCalculationResult) şeklinde direkt olarak atarsanız, bileşen *her yeniden render edildiğinde* expensiveCalculationResult tekrar hesaplanmaya çalışılır. React, başlangıç değerini sadece ilk render'da kullandığı için bu hesaplamanın sonucunu sonraki render'larda atlayacaktır; ancak o maliyetli hesaplama yine de yapılır ve sonra çıkan sonuç atılır. Bu gereksiz hesaplama, uygulamanızın CPU döngülerini israf etmesine ve genel yanıt verme hızının düşmesine neden olur. Küçük objeler veya basit değerler için bu fark hissedilmez olabilir, ancak büyük veri setleri, karmaşık veri yapıları veya yoğun hesaplamalar içeren durumlar için bu durum ciddi bir performans darboğazı haline gelir. İşte tam da bu noktada, fonksiyonel güncellemeler ve özellikle tembel başlatma (lazy initialization) devreye girer ve bu gizli maliyeti ortadan kaldırarak önemli performans iyileştirmeleri sunar.

Çözüm Yolu: useState(() => ...) ile Fonksiyonel Güncellemeler Nasıl Fark Yaratır?

React'ta performanslı state yönetimi için kilit bir teknik olan fonksiyonel güncellemeler, özellikle useState hook'u ile birlikte kullanıldığında, "eager initialization" sorununu ortadan kaldırır ve gereksiz maliyetli hesaplamaları önler. useState(() => ...) yapısı, başlangıç değerinin sadece bileşenin ilk kez render edildiğinde hesaplanmasını sağlayarak "lazy initialization" (tembel başlatma) prensibini uygular. Bu, büyük ve karmaşık objelerin veya pahalı hesaplama gerektiren verilerin yalnızca bir kez oluşturulmasını garanti eder, bu da özellikle performans kritik uygulamalarda önemli bir kazanç sağlar.

Peki, bu tam olarak nasıl işler? useState'e başlangıç değeri olarak doğrudan bir obje veya değer yerine bir fonksiyon geçirdiğinizde, React bu fonksiyonu sadece bileşen ilk kez DOM'a bağlandığında çalıştırır. Fonksiyonun dönüş değeri, state'in ilk değeri olarak atanır. Sonraki yeniden render'larda ise React, bu başlangıç fonksiyonunu tekrar çağırmaz; bunun yerine, zaten bellekte tuttuğu mevcut state değerini kullanır. Bu mekanizma sayesinde, maliyetli hesaplamalar veya büyük obje oluşturma işlemleri yalnızca gerektiğinde, yani bileşenin ömrü boyunca bir kez gerçekleştirilir. İşte bir örnek:


import React, { useState } from 'react';

// Maliyetli bir hesaplama yapan fonksiyon
const buyukVeriOlusturucu = () => {
  console.log("Büyük veri hesaplanıyor...");
  // Örneğin, 1000 elemanlı bir dizi veya karmaşık bir obje
  return Array.from({ length: 1000 }, (_, i) => ({ id: i, value: Item ${i} }));
};

function PerformansliListe() {
  // Tembel başlatma ile sadece ilk render'da çalışır
  const [listeVerisi, setListeVerisi] = useState(buyukVeriOlusturucu);
  const [filtreMetni, setFiltreMetni] = useState('');

  const filtrelenmisListe = listeVerisi.filter(item =>
    item.value.toLowerCase().includes(filtreMetni.toLowerCase())
  );

  return (
    
setFiltreMetni(e.target.value)} />
    {filtrelenmisListe.map(item => (
  • {item.value}
  • ))}
); }

Yukarıdaki örnekte, buyukVeriOlusturucu fonksiyonu, useState'e doğrudan değil, bir fonksiyon referansı olarak geçirilmiştir. Bu sayede, "Büyük veri hesaplanıyor..." mesajını konsolda sadece bir kez, PerformansliListe bileşeni ilk kez oluşturulduğunda görürsünüz. Eğer useState(buyukVeriOlusturucu()) şeklinde direkt fonksiyon çağrısıyla yapsaydık, filtreMetni değiştiğinde bileşen her yeniden render edildiğinde buyukVeriOlusturucu fonksiyonu tekrar çağrılacak ve gereksiz yere liste verisi yeniden hesaplanacaktı. Fonksiyonel güncelleme, bu tür maliyetli işlemlerden kaçınarak uygulamanızın genel duyarlılığını ve hızını artırır.

Bu tembel başlatma prensibi sadece başlangıç değerleri için değil, aynı zamanda state güncellemeleri için de geçerlidir. Bir state'i güncellerken mevcut state değerine ihtiyaç duyuyorsanız, doğrudan yeni bir obje geçirmek yerine bir güncelleme fonksiyonu geçirmek daha güvenli ve performanslıdır. Örneğin, setListeVerisi(prevListe => [...prevListe, yeniEleman]) şeklinde bir kullanım, React'ın güncellemeleri doğru sırayla işlemesini sağlar ve potansiyel yarış koşullarını önler. Bu yöntemler, özellikle mobil cihazlarda veya sınırlı kaynaklara sahip ortamlarda çalışan uygulamalar için kritik öneme sahiptir. Bellek kullanımı azalır, CPU döngüleri boş yere harcanmaz ve kullanıcılar daha akıcı bir deneyim elde ederler. Bu sayede, React uygulamalarınız sadece işlevsel olmakla kalmaz, aynı zamanda hızlı ve duyarlı bir şekilde çalışır.

Vaka Analizi: Büyük Bir Veri Kümesini Yönetirken Performans Farkı Nasıl Ortaya Çıkar?

Şimdi gerçek bir senaryo üzerinde, useState(() => ...) ile useState({...}) arasındaki performans farkını daha net gözlemleyelim. Bir e-ticaret uygulamasında, binlerce ürünün listelendiği bir sayfa hayal edelim. Kullanıcı, bu ürünleri çeşitli filtrelere göre (fiyat aralığı, kategori, marka vb.) sıralayabiliyor veya arama kutusuna metin girerek filtreleyebiliyor. Ürün verisinin kendisi oldukça büyük olabilir ve her bir ürün objesi birden fazla özelliği içerebilir (id, ad, açıklama, resimler, fiyat, stok durumu vb.).

Senaryo 1: useState({...}) ile "Eager Initialization" (Performans Sorunlu Yaklaşım)

Bu senaryoda, başlangıçtaki tüm ürün verisini doğrudan useState'e aktarıyoruz ve ürünlerin filtreleme mantığını her state güncellemesinde tekrar çalıştırıyoruz.


import React, { useState, useEffect } from 'react';

// Çok sayıda ürün oluşturan maliyetli bir fonksiyon
const generateLargeProductList = () => {
  console.log("Maliyetli ürün listesi oluşturuluyor (eager)...");
  const products = [];
  for (let i = 0; i < 5000; i++) { // 5000 ürün
    products.push({
      id: i,
      name: Ürün Adı ${i},
      description: Bu ${i}. ürünün açıklama metnidir.,
      price: Math.floor(Math.random() * 100) + 10,
      category: Kategori ${i % 5},
      imageUrl: https://example.com/product-${i}.jpg,
      details: Array.from({ length: 10 }, (_, j) => Detay ${j} for ${i}) // Daha karmaşık bir obje için
    });
  }
  return products;
};

function EagerProductList() {
  const [allProducts, setAllProducts] = useState(generateLargeProductList()); // PROBLEMLİ KISIM!
  const [searchTerm, setSearchTerm] = useState('');
  const [filteredProducts, setFilteredProducts] = useState([]);

  useEffect(() => {
    // Her allProducts veya searchTerm değiştiğinde filtreleme yapılır
    console.log("Ürünler filtreleniyor...");
    const results = allProducts.filter(product =>
      product.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
      product.description.toLowerCase().includes(searchTerm.toLowerCase())
    );
    setFilteredProducts(results);
  }, [allProducts, searchTerm]);

  console.log("EagerProductList yeniden render edildi.");

  return (
    

Eager Başlatma ile Ürün Listesi

setSearchTerm(e.target.value)} />
    {filteredProducts.map(product => (
  • {product.name} - ${product.price}
  • ))}
); }

Bu örnekte, EagerProductList bileşeni her yeniden render edildiğinde (örneğin, searchTerm değiştiğinde), useState(generateLargeProductList()) satırı tekrar çalıştırılmaya çalışılır. Her ne kadar React sadece ilk render'da bu değeri kullansa da, JavaScript motoru generateLargeProductList() fonksiyonunu her render'da yeniden çağırır ve bu da "Maliyetli ürün listesi oluşturuluyor (eager)..." mesajını konsolda her render'da görmemize neden olur. Bu gereksiz hesaplama, uygulamanızın yanıt verme süresini ciddi şekilde uzatır ve kullanıcı arayüzünde takılmalara yol açabilir.

Senaryo 2: useState(() => ...) ile "Lazy Initialization" (Performans Odaklı Yaklaşım)

Şimdi aynı senaryoyu, useState'in fonksiyonel başlatma özelliğini kullanarak optimize edelim.


import React, { useState, useEffect } from 'react';

// Çok sayıda ürün oluşturan maliyetli bir fonksiyon
const generateLargeProductListLazy = () => {
  console.log("Maliyetli ürün listesi oluşturuluyor (lazy)...");
  const products = [];
  for (let i = 0; i < 5000; i++) { // Aynı 5000 ürün
    products.push({
      id: i,
      name: Ürün Adı ${i},
      description: Bu ${i}. ürünün açıklama metnidir.,
      price: Math.floor(Math.random() * 100) + 10,
      category: Kategori ${i % 5},
      imageUrl: https://example.com/product-${i}.jpg,
      details: Array.from({ length: 10 }, (_, j) => Detay ${j} for ${i})
    });
  }
  return products;
};

function LazyProductList() {
  const [allProducts, setAllProducts] = useState(generateLargeProductListLazy); // PERFORMANSLI KISIM!
  const [searchTerm, setSearchTerm] = useState('');
  const [filteredProducts, setFilteredProducts] = useState([]);

  useEffect(() => {
    // allProducts referansı değişmediği için sadece searchTerm değiştiğinde filtreleme tetiklenir
    console.log("Ürünler filtreleniyor...");
    const results = allProducts.filter(product =>
      product.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
      product.description.toLowerCase().includes(searchTerm.toLowerCase())
    );
    setFilteredProducts(results);
  }, [allProducts, searchTerm]);

  console.log("LazyProductList yeniden render edildi.");

  return (
    

Lazy Başlatma ile Ürün Listesi

setSearchTerm(e.target.value)} />
    {filteredProducts.map(product => (
  • {product.name} - ${product.price}
  • ))}
); }

Bu ikinci senaryoda, useState(generateLargeProductListLazy) kullanımı sayesinde generateLargeProductListLazy fonksiyonu sadece bileşen ilk kez monte edildiğinde çağrılır. "Maliyetli ürün listesi oluşturuluyor (lazy)..." mesajını konsolda sadece bir kez görürsünüz. Sonraki searchTerm değişikliklerinde bileşen yeniden render olsa bile, başlangıç fonksiyonu tekrar çalıştırılmaz. Bu, özellikle büyük veri setleriyle çalışırken CPU ve bellek kaynaklarından önemli ölçüde tasarruf etmenizi sağlar. Kullanıcı, arama kutusuna her harf yazdığında gereksiz yere binlerce objenin yeniden oluşturulması maliyetinden kurtulmuş oluruz. Sonuç olarak, uygulama çok daha hızlı tepki verir ve akıcı bir kullanıcı deneyimi sunar. Bu vaka analizi, küçük bir kod değişikliğinin büyük ölçekli uygulamalarda ne kadar kritik bir performans iyileştirmesi sağlayabileceğini açıkça göstermektedir.

İleri Seviye Optimizasyonlar: useEffect ve useCallback ile Fonksiyonel Güncellemeleri Birleştirmek

Fonksiyonel güncellemeler, useState performansını artırmanın tek yolu değildir; diğer React hook'ları ile birlikte kullanıldığında çok daha güçlü bir optimizasyon stratejisi oluşturur. Özellikle useEffect ve useCallback, state yönetiminizde fonksiyonel güncellemeleri tamamlayıcı roller üstlenir. Bu üçlüyü doğru bir şekilde birleştirmek, özellikle karmaşık ve veri yoğun uygulamalarda fark edilir performans artışları sağlayabilir. Şimdi bu ileri seviye entegrasyonlara daha yakından bakalım.

useCallback ve Fonksiyonel Güncellemeler

useCallback hook'u, bir fonksiyonu (genellikle bir event handler veya bir prop olarak alt bileşene iletilen bir fonksiyonu) memoize etmenizi sağlar. Yani, fonksiyonun referansı, bağımlılıkları değişmediği sürece aynı kalır. Bu, özellikle alt bileşenlere prop olarak fonksiyonlar geçirildiğinde ve bu alt bileşenlerin React.memo ile optimize edildiği durumlarda çok önemlidir. Eğer bir üst bileşen her render olduğunda yeni bir fonksiyon referansı oluşturursa, React.memo ile optimize edilmiş alt bileşenler bile gereksiz yere yeniden render edilir. İşte burada useState'in fonksiyonel güncellemeleri devreye girer.

Eğer bir event handler içinde useState setter fonksiyonunu kullanırken mevcut state'e bağımlı bir güncelleme yapıyorsanız, bu setter'a doğrudan yeni bir değer yerine bir fonksiyon geçirmek, useCallback ile birlikte kullanıldığında daha verimli olabilir. Çünkü bu durumda, useCallback bağımlılık dizisine mevcut state'i (örneğin count'u) eklemenize gerek kalmaz:


import React, { useState, useCallback } from 'react';

function SayacKontrolcusu() {
  const [sayac, setSayac] = useState(0);

  // setSayac(sayac + 1) yerine setSayac(prevSayac => prevSayac + 1) kullanımı
  // bu useCallback'in bağımlılık dizisine 'sayac' eklememizi gerektirmez.
  const artirSayac = useCallback(() => {
    setSayac(prevSayac => prevSayac + 1);
  }, []); // Bağımlılık dizisi boş olduğu için bu fonksiyon referansı değişmez

  const azaltSayac = useCallback(() => {
    setSayac(prevSayac => prevSayac - 1);
  }, []); // Bağımlılık dizisi boş olduğu için bu fonksiyon referansı değişmez

  console.log("SayacKontrolcusu render edildi.");

  return (
    

Mevcut Sayaç: {sayac}

); }

Bu örnekte, artirSayac ve azaltSayac fonksiyonları, useCallback sayesinde bileşen her render olduğunda yeniden oluşturulmaz. Ve çünkü setSayac fonksiyonuna bir önceki state'i alan bir fonksiyon (prevSayac => prevSayac + 1) geçiriyoruz, sayac değişkeninin kendisini useCallback'in bağımlılık dizisine eklemeye gerek kalmaz. Bu, hem fonksiyon referansının stabil kalmasını sağlar hem de gereksiz kapanış (closure) maliyetlerini önler.

useEffect ve Fonksiyonel Güncellemeler

useEffect hook'u, bileşenin yaşam döngüsü boyunca yan etkileri (veri çekme, DOM manipülasyonu, timer ayarlama vb.) yönetmek için kullanılır. useEffect'in doğru kullanımı için bağımlılık dizisi (dependency array) kritik öneme sahiptir. Eğer bağımlılık dizisindeki bir değer değişirse, effect fonksiyonu yeniden çalışır. Burada da fonksiyonel güncellemeler bize yardımcı olabilir.

Bazı durumlarda, useEffect içinde bir state'i güncellemek isteyebiliriz ve bu güncelleme mevcut state'e bağlı olabilir. Eğer bu state'i useEffect'in bağımlılık dizisine eklerseniz, state her değiştiğinde effect tekrar çalışır ve bu bir sonsuz döngüye veya gereksiz hesaplamalara yol açabilir. Fonksiyonel güncellemeler, bu sorunu çözmek için bir yol sunar:


import React, { useState, useEffect } from 'react';

function Timer() {
  const [saniye, setSaniye] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      // setSaniye(saniye + 1) yerine fonksiyonel güncelleme
      // bu sayede 'saniye' değişkenini bağımlılık dizisine eklemeye gerek kalmaz.
      setSaniye(prevSaniye => prevSaniye + 1);
    }, 1000);

    return () => clearInterval(intervalId); // Bileşen unmount edildiğinde temizleme
  }, []); // Bağımlılık dizisi boş, çünkü setSaniye fonksiyon referansı sabittir ve prevSaniye kullanılıyor.

  return 

Geçen Süre: {saniye} saniye

; }

Bu örnekte, setSaniye(prevSaniye => prevSaniye + 1) kullanımı sayesinde, saniye değişkenini useEffect'in bağımlılık dizisine eklemeye gerek kalmaz. Bu, setInterval'ın sadece bir kez ayarlanmasını ve her saniye güncelleme fonksiyonu aracılığıyla state'in doğru bir şekilde artırılmasını sağlar. Eğer setSaniye(saniye + 1) kullansaydık ve saniye'yi bağımlılık dizisine eklemeseydik, setInterval her zaman ilk saniye değerini (0) yakalayacak ve sayacımız hiç artmayacaktı. Eğer saniye'yi bağımlılık dizisine ekleseydik, saniye her değiştiğinde setInterval temizlenip yeniden ayarlanacaktı ki bu da verimsiz bir yaklaşımdır. Görüldüğü üzere, fonksiyonel güncellemeler, useEffect ve useCallback gibi diğer önemli hook'larla birleştiğinde React uygulamalarınızın hem performansını hem de kod kalitesini önemli ölçüde artırabilir.

Sık Yapılan Hatalar ve Kaçınılması Gerekenler: Her Durumda Fonksiyonel Güncelleme Kullanmalı Mıyız?

React'ta fonksiyonel güncellemeler, özellikle performans optimizasyonu ve state tutarlılığı açısından önemli avantajlar sunsa da, her durumda tek doğru çözüm değildir. Geliştiricilerin bu tekniği ne zaman kullanacaklarını ve ne zaman geleneksel yaklaşımların yeterli olacağını anlamaları, gereksiz karmaşıklıktan kaçınmak ve okunabilirliği korumak açısından kritik öneme sahiptir. Yanlış yerlerde veya yanlış şekillerde kullanıldığında, fonksiyonel güncellemeler beklenmedik sorunlara yol açabilir veya kodunuzu gereksiz yere karmaşık hale getirebilir.

Ne Zaman Kullanmamalıyız?

  1. Basit ve Maliyeti Olmayan Değerler İçin: Eğer state'iniz sadece bir sayı, bir boolean veya küçük, sabit bir metin dizisi gibi basit bir ilkel değerden oluşuyorsa ve bu değerin başlangıcı veya güncellemesi herhangi bir maliyetli hesaplama gerektirmiyorsa, fonksiyonel güncellemeler kullanmak genellikle gereksizdir. Örneğin:
    
            const [aktif, setAktif] = useState(false); // Doğrudan boolean yeterli
            const [kullaniciAdi, setKullaniciAdi] = useState('Misafir'); // Doğrudan string yeterli
            


    Bu durumlarda, useState(() => false) veya setAktif(prevAktif => !prevAktif) gibi bir yaklaşım, okunabilirliği azaltabilir ve herhangi bir performans artışı sağlamaz.

  2. State Güncellemesi Mevcut State'e Bağlı Değilse: Eğer bir state güncellemesi yaparken, yeni değerin mevcut state değerinden bağımsız olduğu durumlarda fonksiyonel güncelleme kullanmak anlamsızdır. Örneğin, bir API çağrısından gelen veriyi doğrudan state'e set ediyorsanız:
    
            useEffect(() => {
              fetch('/api/veri').then(res => res.json()).then(data => {
                setVeri(data); // Doğrudan data göndermek yeterli
              });
            }, []);
            


    Burada setVeri(prevVeri => data) şeklinde bir kullanım, prevVeri argümanının aslında kullanılmaması nedeniyle gereksiz bir soyutlama olacaktır.

Kaçınılması Gereken Hatalar:

  • Yanlış Bağımlılıklar: useCallback veya useEffect içinde fonksiyonel güncellemeler kullanırken, bağımlılık dizilerini yanlış ayarlamak yaygın bir hatadır. Fonksiyonel güncellemeler sayesinde bazen bağımlılık dizisinden bir değeri çıkarabilirsiniz (yukarıdaki Timer örneğindeki saniye gibi). Ancak, eğer fonksiyonel güncelleyicinizin içinde, prevSaniye dışındaki bir kapsayıcı kapsamdan gelen bir değişkene (prop veya başka bir state) ihtiyacınız varsa, bu değişkeni bağımlılık dizisine *mutlaka* eklemelisiniz. Aksi takdirde, closure içindeki eski bir değere referans tutarak beklenmedik davranışlara yol açabilirsiniz.
  • Gereksiz Karmaşıklık: Her state güncellemesinde fonksiyonel yaklaşım kullanmaya çalışmak, kodunuzu okumayı ve anlamayı zorlaştırabilir. Kodun amacı basit ve açıksa, en basit yöntemi tercih etmek genellikle en iyi yaklaşımdır. "Premature optimization" (erken optimizasyon) tuzağına düşmekten kaçınmak önemlidir. Performans sorunu gözlemlemediğiniz sürece, basit yaklaşımlara sadık kalmak daha iyidir.
  • Karmaşık Obje Güncellemelerinde Eksik Kopyalama: Fonksiyonel güncellemelerle karmaşık objeleri güncellerken, değişen kısmı doğru bir şekilde kopyaladığınızdan emin olun. Örneğin, iç içe geçmiş bir objede sadece bir alanı güncelliyorsanız, o alanın üstündeki tüm objelerin kopyalanması gerekir (immutability).
    
            setKullanici(prevKullanici => ({
              ...prevKullanici,
              adres: {
                ...prevKullanici.adres,
                sehir: 'Ankara'
              }
            }));
            


    Eğer bu kopyalamayı doğru yapmazsanız, React objenin referansının değişmediğini düşünür ve arayüzü güncellemez.

Sonuç olarak, useState(() => ...) kullanımını bir "gümüş kurşun" olarak görmek yerine, belirli senaryolarda, özellikle maliyetli başlangıç değerleri veya mevcut state'e bağımlı güncellemeler söz konusu olduğunda güçlü bir optimizasyon aracı olarak değerlendirmek en sağlıklısıdır. Her zaman uygulamanızın ihtiyaçlarını, okunabilirliği ve performansı dengelemeye çalışın.

Mobil Uygulamalarda Performansın Önemi: Duyarlı Tasarım ve JavaScript Optimizasyonu

Mobil cihazlar, dünya genelinde internet kullanımının büyük bir kısmını oluşturmaktadır ve React tabanlı uygulamaların (hem web hem de React Native) mobil performansları kritik bir öneme sahiptir. Mobil cihazların işlem gücü, bellek kapasitesi ve ağ bağlantısı, masaüstü bilgisayarlara kıyasla genellikle daha sınırlıdır. Bu durum, mobil uygulamaların performans optimizasyonunu bir zorunluluk haline getirir. useState(() => ...) gibi JavaScript optimizasyonları, mobil cihazlarda CPU döngülerinden ve bellekten tasarruf etmenin temel yollarından biridir, ancak mobil performans sadece JavaScript optimizasyonlarıyla sınırlı değildir; duyarlı tasarım ve genel kaynak yönetimi de büyük rol oynar.

Duyarlı Tasarım ve Kullanıcı Deneyimi

Mobil uyumlu HTML ve CSS kullanımı, React uygulamalarının farklı ekran boyutlarına ve cihazlara sorunsuz bir şekilde adapte olmasını sağlar. Duyarlı tasarım (Responsive Design), uygulamanızın küçük bir telefon ekranından büyük bir tablete kadar her cihazda iyi görünmesini ve işlevsel olmasını garanti eder. Bu, sadece CSS medya sorguları (media queries) ile sınırlı değildir; aynı zamanda bileşenlerin esnekliğini, resimlerin optimizasyonunu ve dokunmatik etkileşimleri desteklemesini de kapsar.


/* Örnek bir mobil uyumlu CSS medya sorgusu */
.kapsayici {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-around;
}

@media (max-width: 768px) { /* Telefon ve küçük tablet ekranları için */
  .kapsayici {
    flex-direction: column; /* Mobil cihazlarda dikey sıralama */
    align-items: center; /* Ortala */
  }

  .urun-karti {
    width: 90%; /* Genişliği %90 yap */
    margin-bottom: 20px;
  }
}

Bu tür CSS optimizasyonları, görsel performans için önemlidir. Ancak, React uygulamalarında asıl performans darboğazları genellikle JavaScript yürütme süresinden kaynaklanır. Karmaşık DOM manipülasyonları, büyük veri kümelerinin işlenmesi veya gereksiz yeniden render'lar, mobil cihazların sınırlı işlemcilerini zorlayabilir.

JavaScript Optimizasyonu ve Mobil Performans

useState(() => ...) tekniği, mobil cihazlarda özellikle önemlidir çünkü her bir CPU döngüsü ve her bir bellek bloğu daha değerlidir. Gereksiz yere hesaplanan veya oluşturulan büyük objeler, sadece CPU'yu meşgul etmekle kalmaz, aynı zamanda bellek kullanımını artırarak uygulamanın genel yanıt verme hızını düşürür ve hatta cihazın pil ömrünü olumsuz etkileyebilir. Özellikle React Native uygulamalarında, JavaScript köprüsü (bridge) üzerinden yerel modüllerle iletişim kurmanın getirdiği ek maliyetler göz önüne alındığında, JavaScript tarafındaki her türlü optimizasyonun etkisi katlanarak artar.

Mobil performans için diğer önemli JavaScript optimizasyonları şunlardır:

  • Kod Bölümleme (Code Splitting): Uygulamanızın tüm kodunu tek bir büyük paket yerine daha küçük parçalara ayırmak, ilk yükleme süresini kısaltır. React.lazy ve Suspense bu konuda yardımcı olur.
  • Gereksiz Yeniden Render'lardan Kaçınma: React.memo, useCallback ve useMemo gibi hook'ları kullanarak bileşenlerin ve fonksiyonların gereksiz yere yeniden render edilmesini veya hesaplanmasını engelleyin.
  • Sanallaştırma (Virtualization): Büyük listelerle çalışırken (örneğin binlerce elemanlı bir ürün listesi), sadece ekranda görünen elemanları render etmek için react-window veya react-virtualized gibi kütüphaneleri kullanın. Bu, DOM boyutunu ve render maliyetini önemli ölçüde azaltır.
  • Debounce ve Throttle: Sık tetiklenen event handler'lar (örneğin arama kutusundaki onChange veya pencere boyutlandırma event'leri) için debounce veya throttle tekniklerini kullanarak fonksiyonların çok sık çağrılmasını engelleyin.
  • Doğru State Yönetimi: Global state'i dikkatli yönetin ve sadece ilgili bileşenlerin güncellenmesini sağlayın. Context API veya Redux gibi çözümleri verimli kullanın.

Sonuç olarak, mobil uygulamaların geliştirilmesinde performans, göz ardı edilemeyecek bir unsurdur. useState(() => ...) gibi küçük ama etkili JavaScript optimizasyonlarından, duyarlı tasarıma ve kapsamlı kod yönetimine kadar birçok farklı tekniği bir araya getirerek, kullanıcılara mobil cihazlarda hızlı, akıcı ve keyifli bir deneyim sunabiliriz.

Sonuç: React Performansını Artırmak İçin Anahtar İpuçları Nelerdir?

React uygulamalarının performansını artırmak, kullanıcı deneyimini doğrudan etkileyen ve uygulamanızın başarısı için kritik bir faktördür. Bu makale boyunca, useState hook'unun özel bir kullanım biçimi olan fonksiyonel başlatma ve güncellemenin, özellikle maliyetli hesaplamalar veya büyük veri yapıları söz konusu olduğunda nasıl önemli performans avantajları sağladığını detaylı bir şekilde inceledik. useState(() => ...) yaklaşımı, gereksiz yeniden render'ları ve CPU döngüsü israfını önleyerek uygulamanızın daha hızlı ve daha duyarlı çalışmasına olanak tanır. Ancak, React performansını artırmak sadece bu teknikle sınırlı değildir; bir bütün olarak ele alınması gereken çok yönlü bir konudur.

Özetle, React uygulamanızda performansı optimize etmek için aşağıdaki anahtar ipuçlarını aklınızda bulundurmalısınız:

  • useState ile Fonksiyonel Güncellemeler (Lazy Initialization): Maliyetli başlangıç değerleri veya mevcut state'e bağlı güncellemeler yaparken her zaman useState(() => değer) veya setDeger(prevDeger => yeniDeger) formunu tercih edin. Bu, gereksiz hesaplamaları ve obje oluşturmaları önleyerek özellikle büyük uygulamalarda belirgin bir performans artışı sağlar.
  • Memoization Kullanın: React.memo, useCallback ve useMemo hook'larını kullanarak gereksiz yeniden render'ları, fonksiyon yeniden oluşturmalarını ve maliyetli hesaplamaları önleyin. Bu, özellikle karmaşık ve veri yoğun bileşenler için çok önemlidir.
  • Doğru State Yapılandırması: State'inizi atomik ve düz (flat) tutmaya çalışın. Çok iç içe geçmiş veya geniş objelerde değişiklik yaparken, sadece değişen kısmı kopyalamak yerine tüm objenin referansını değiştirmek, React'ın işini zorlaştırır.
  • Sadece Gerekeni Render Edin (Virtualization): Uzun listeler veya tablolar gibi büyük veri koleksiyonlarını görüntülerken, react-window veya react-virtualized gibi kütüphaneleri kullanarak sadece ekranda görünen elemanları render edin.
  • Optimize Edilmiş Görsel Kaynaklar: Resimleri optimize edin, uygun formatlarda kullanın ve gerektiğinde geç yükleme (lazy loading) uygulayın. CSS'i verimli kullanın, gereksiz animasyonlardan kaçının ve karmaşık düzenleri optimize edin.
  • Kod Bölümleme (Code Splitting): Uygulamanızın başlangıç yükleme süresini kısaltmak için React.lazy ve Suspense ile kodunuzu parçalara ayırın.
  • Debounce ve Throttle: Sık tetiklenen olay dinleyicileri (örneğin arama kutusu girişi) için debounce ve throttle tekniklerini kullanarak fonksiyonların gereksiz yere çalışmasını engelleyin.

Unutmayın ki optimizasyon bir süreçtir ve her zaman uygulamanızın özel ihtiyaçlarına göre şekillenmelidir. Erken optimizasyon tuzağına düşmek yerine, performans sorunları ortaya çıktığında veya projeniz büyüdükçe bu teknikleri stratejik olarak uygulamak daha verimli olacaktır. Geliştirdiğiniz React uygulamalarının sadece işlevsel değil, aynı zamanda hızlı, akıcı ve keyifli bir kullanıcı deneyimi sunmasını sağlayın!

Sıkça Sorulan Sorular

1. useState(() => ...) her zaman daha mı iyidir?

Hayır, her zaman daha iyi değildir. Bu yöntem, özellikle başlangıç değeri maliyetli bir hesaplama gerektirdiğinde veya büyük bir obje/dizi olduğunda performans avantajı sağlar. Basit ilkel değerler (sayı, boolean, string) veya küçük objeler için doğrudan değer vermek yeterlidir ve kodun okunabilirliğini artırabilir. Gereksiz yere fonksiyonel başlatma kullanmak, küçük uygulamalarda fayda sağlamaz, hatta kodunuzu biraz daha karmaşık gösterebilir.

2. Karmaşık objelerde derin kopyalama yapmak ne kadar önemlidir?

Çok önemlidir. React, state güncellemelerinde objelerin veya dizilerin referans eşitliğini kontrol eder. Eğer karmaşık bir objenin içindeki bir özelliği güncellerken, objenin dış referansını değiştirmezseniz (yani derin kopyalama yapmazsanız), React bu objeyi değişmemiş olarak algılar ve bileşeni yeniden render etmeyebilir. Bu durum, UI'ınızın güncellenmemesine neden olur. Her zaman immutability prensibine uyarak, değişen objenin yeni bir kopyasını oluşturmalı ve bu kopyayı state olarak ayarlamalısınız ({ ...prevObj, icOzellik: yeniDeger }).

3. Bu optimizasyon küçük uygulamalar için de geçerli mi?

Küçük uygulamalar için bu optimizasyonun etkisi genellikle göz ardı edilebilir düzeydedir. Performans kazanımları, genellikle büyük veri setleri, yoğun hesaplamalar veya çok sayıda bileşenin sık sık yeniden render edildiği karmaşık uygulamalarda daha belirgin hale gelir. Ancak, iyi bir pratik olarak benimsemek, uygulamanız büyüdükçe potansiyel performans sorunlarını baştan engellemenize yardımcı olabilir.

4. Fonksiyonel güncellemeler sadece useState ile mi kullanılır?

useState hook'unun setter fonksiyonu (örneğin setSayac) için fonksiyonel güncellemeler temel kullanım alanıdır. Ancak, React'ta state'i güncelleyen diğer yöntemlerde (örneğin Redux'taki reducer'lar) veya callback fonksiyonlarında da mevcut state'e bağımlı güncellemeler yaparken fonksiyonel yaklaşımlar tercih edilebilir. Bu, özellikle asenkron güncellemeler veya mevcut state'in bir önceki versiyonuna ihtiyaç duyduğunuz durumlarda state tutarlılığını sağlamak için önemlidir.

5. Performansı ölçmek için hangi araçları kullanmalıyım?

React uygulamalarının performansını ölçmek için çeşitli araçlar mevcuttur:

  • React Developer Tools Profiler: Tarayıcı eklentisi olarak mevcuttur. Bileşenlerin ne sıklıkla render edildiğini, render sürelerini ve hangi bileşenlerin en fazla zaman harcadığını detaylı bir şekilde görmenizi sağlar.
  • Lighthouse: Google Chrome tarayıcısında yerleşik bir denetleme aracıdır. Performans, erişilebilirlik, en iyi uygulamalar ve SEO gibi metrikler üzerinden uygulamanızın genel sağlığını değerlendirir.
  • Tarayıcı Geliştirici Araçları (Performance Tab): Chrome, Firefox gibi tarayıcıların geliştirici araçlarındaki "Performance" veya "Profiler" sekmesi, JavaScript yürütme süresi, render süreçleri, bellek kullanımı ve ağ istekleri gibi düşük seviyeli performans metriklerini incelemenizi sağlar.
  • Web Vitals: Google'ın Core Web Vitals metrikleri (LCP, FID, CLS), kullanıcı deneyimi açısından önemli olan gerçek dünya performansını ölçer.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Exit mobile version