State Yönetimi ve useState
React Hooks'un en temel ve devrim niteliğindeki bileşenlerinden biri olan useState, fonksiyonel bileşenlere durum(state) ekleme yeteneği kazandırmıştır. Klasik sınıf bileşenlerindeki `this.state` ve `this.setState` metodlarının karmaşıklığını ortadan kaldırarak, daha okunabilir ve test edilebilir bir kod yapısı sunar. Bu Hook, bileşenin yaşam döngüsü boyunca değişebilen ve yeniden render işlemini tetikleyen değerleri yönetmek için tasarlanmıştır.
useState Hook'u, bir başlangıç değeri alır ve bir dizi döndürür. Bu dizinin ilk elemanı mevcut state değeridir. İkinci elemanı ise o state'i güncellemek için kullanılan bir fonksiyondur. Bu fonksiyon çağrıldığında, bileşen yeni state değeri ile birlikte yeniden render edilir. State güncellemeleri asenkron olarak gerçekleşir, bu nedenle bir önceki state değerine bağlı güncellemeler yapılırken dikkatli olunmalıdır. Bu durum, bir callback fonksiyonu kullanarak aşılabilir.
İyi bir uygulama, ilgili state değişkenlerini ayrı ayrı tanımlamaktır. Tek bir büyük state objesi yerine, birbirinden bağımsız birden fazla `useState` çağrısı kullanmak, state güncellemelerini daha tahmin edilebilir ve izlenebilir kılar. Örneğin, bir form bileşeninde `email` ve `password` için ayrı state'ler tanımlamak, her birinin değerini bağımsız olarak yönetmeyi sağlar ve hata ayıklamayı kolaylaştırır.
Karmaşık state yapıları için, birden fazla `useState` kullanmak yerine `useReducer` Hook'unu değerlendirmek daha iyi bir seçenek olabilir. Ancak basit ve lokal durum yönetimi söz konusu olduğunda, `useState` vazgeçilmezdir. Performans optimizasyonu açısından, state güncelleme fonksiyonunun bileşenin her render'ında yeniden oluşturulmadığını bilmek önemlidir; React bu fonksiyonun referansını sabit tutar.
State'i kaldırma yukarı taşıma (lifting state up) prensibi, `useState` ile de geçerlidir. Eğer bir state birden fazla kardeş bileşen tarafından ihtiyaç duyuluyorsa, bu state'in en yakın ortak ata bileşene taşınması gerekir. Bu durumda, state ve onu güncelleyen fonksiyon prop olarak aşağıya iletilir, böylece veri akışı tek yönlü ve merkezi olrak kalır. Bu yaklaşım, uygulamanın genel yapısını sadeleştirir ve beklenmeyen yan etkilerin önüne geçer.
| Kullanım Senaryosu | useState Uygulaması | Kazanım |
|---|---|---|
| Form Input Yönetimi | Her input için ayrı state değişkeni | Gelişmiş izolasyon ve kontrol |
| Açılır/Kapanır Modal | `const [isOpen, setIsOpen] = useState(false)` | Basit ve anlaşılır görünürlük kontrolü |
| Sayaç (Counter) | `const [count, setCount] = useState(0)` | Basit sayısal değer takibi |
| Koşullu Render | `const [isLoading, setIsLoading] = useState(true)` | UI'ın duruma göre değişimi |
Yan Etkiler ve useEffect
React bileşenlerinin ana işlevi, prop'ları ve state'i alıp bir UI görünümü döndürmektir. Ancak modern uygulamalar, yan etkiler (side effects) olarak adlandırılan işlemlere ihtiyaç duyar: veri çekme, abonelikler oluşturma veya DOM'u manuel olarak manipüle etme gibi. İşte useEffect Hook'u, fonksiyonel bileşenlerin içinde bu tür yan etkileri gerçekleştirmenin bir yolunu sağlar. Sınıf bileşenlerindeki `componentDidMount`, `componentDidUpdate` ve `componentWillUnmount` yaşam döngüsü metodlarının birleşmiş halidir.
useEffect'in temel yapısı, iki argüman alır: bir yan etki fonksiyonu ve isteğe bağlı bir bağımlılık dizisi (dependency array). Yan etki fonksiyonu, bileşen render edildikten sonra çalıştırılır. Eğer bağımlılık dizisi boş bırakılırsa (`[]`), efekt sadece bileşen ilk kez monte edildiğinde çalışır, tıpkı `componentDidMount` gibi. Eğer dizi hiç sağlanmazsa, efekt her render'dan sonra çalışır, bu da genellikle istenmeyen bir performans sorunudur.
Bağımlılık dizisinin en önemli kullanımı, belirli state veya prop değerleri değiştiğinde efektin yeniden çalışmasını sağlamaktır. Örneğin, bir kullanıcı ID'si prop'u değiştiğinde, o kullanıcıya ait verileri yeniden çekmek için efekt bağımlılık dizisine bu prop'u eklemek gerekir. Bu, yan etkileri bileşenin girdileriyle senkronize tutmanın etkili bir yoludur. Bağımlılık dizisini doğru şekilde yönetmek, sonsuz render döngülerini ve bellek sızıntılarını önlemenin anahtarıdır.
Temizleme (cleanup) mekanizması, `useEffect`'in bir diğer kritik özelliğidir. Yan etki fonksiyonundan bir fonksiyon döndürürseniz, React bu fonksiyonu bileşen unmount olmadan önce vya bir sonraki efekt çalıştırılmadan önce çağırır. Bu, abonelikleri iptal etmek, timer'ları temizlemek veya ağ isteklerini sonlandırmak için hayati öneme sahiptir. Örneğin, bir WebSocket bağlantısı veya bir `setInterval` timer'ı oluşturuyorsanız, temizleme fonksiyonu aracılığıyla bu kaynakları serbest bırakmazsanız bellek sızıntıları oluşabilir.
Veri çekme (data fetching), `useEffect`'in en yaygın kullanım alanlarından biridir. Ancak, bu işlemi doğrudan efekt içinde yaparken bazı zorluklarla karşılaşılır: isteklerin çakışması (race conditions), hata yönetimi ve yükleme durumlarının takibi. Bu nedenle, `useEffect` içinde `async/await` kullanmak doğrudan mümkün değildir; bunun yerine asenkron bir iç fonksiyon tanımlanıp çağrılmalıdır. Daha karmaşık senaryolar için, React Query veya SWR gibi özel kütüphanelerin kullanımı daha sağlam bir çözüm sunabilir.
Birden fazla useEffect kancası kullanmak, ilgili yan etkileri mantıksal olarak birbirinden ayırmanın etkili bir yoludur. React, tanımlandıkları sırayla her bir efekti çalıştırır. Bu, farklı bağımlılıklara sahip olan işlemleri (örneğin, bir belge başlığını güncellemek ve bir veri aboneliği oluşturmak) ayrı `useEffect` bloklarında yazmayı mümkün kılar. Bu yaklaşım, kodun anlaşılırlığını ve bakımını önemli ölçüde artırır, çünkü her bir blok tek ve belirli bir sorumluluğu yerine getirir.
Performans optimizasyonu açısından, gereksiz efekt çalıştırmalarından kaçınmak esastır. Büyük hesaplamalar veya pahalı API çağrıları içeren efektler, sık sık çalıştırılmamalıdır. Bağımlılık dizisine yalnızca efektin gerçekten yeniden çalışmasını tetiklemesi gereken değişkenleri eklemek ve gereksiz bağımlılıklardan kaçınmak bu noktada kritik öneme sahiptir. Bazen, bir callback veya değeri memoize etmek için `useCallback` veya `useMemo` kullanmak, bağımlılık dizisini stabilize etmek ve istemsiz efekt yeniden çalıştırmalarını önlemek için gerekli olabilir.
Bağlam ve useReducer
Karmaşık bileşen durumu, birden fazla alt değerden oluştuğunda veya bir sonraki state bir öncekine sıkı sıkıya bağlı olduğunda, `useState` yönetimi zorlaşabilir. Bu noktada, useReducer Hook'u devreye girer. `useReducer`, state geçişlerini merkezi bir yerde, yani bir reducer fonksiyonunda yönetmenize olanak tanıyarak, karmaşık state mantığını daha öngrülebilir ve test edilebilir hale getirir. Redux kütüphanesindeki reducer konseptiyle birebir aynı prensibi izler, ancak bileşen seviyesinde çalışır.
`useReducer`, bir reducer fonksiyonu ve bir başlangıç state'i alır. Geriye, mevcut state'i ve bir `dispatch` fonksiyonunu döndürür. `Dispatch` fonksiyonu, bir "eylem (action)" objesi alır ve reducer'ı çağırarak state'i bu eyleme göre günceller. Eylem objesi, genellikle bir `type` alanı ve isteğe bağlı bir `payload` alanı içerir. Bu yapı, state güncelleme mantığının bileşenin dışına çıkarılmasını sağlar, bu da mantığı izole etmek ve tekrar kullanmak için idealdir.
useReducer'ın en güçlü olduğu yer, birbiriyle ilişkili birden fazla state değişkeninin olduğu durumlardır. Örneğin, bir formun doğrulama durumu, gönderim durumu ve hata mesajları gibi. Bu değişkenleri tek bir `useState` objesinde tutmak, güncellemeleri yönetmeyi zorlaştırır. Ancak bir reducer kullanarak, `'SUBMIT_SUCCESS'` gibi bir eylem tipi için tüm ilgili state alanlarını tek bir yerden ve tutarlı bir şekilde güncelleyebilirsiniz. Bu, bileşeninizin state mantığını daha temiz hale getirir.
- useState için ideal: Basit, lokal, birbirinden bağımsız değerler (örn: toggle, sayıcı, tek input).
- useReducer için ideal: Karmaşık, iç içe geçmiş state yapıları, bir sonraki state öncekine bağlıysa veya birden fazla alt değer aynı eylemle değişiyorsa.
- Bağlam (Context) ile birlikte: `useReducer`, global state yönetimi için `useContext` ile mükemmel bir ikili oluşturur. Reducer'ı bir bağlam sağlayıcısının (Context Provider) değeri olarak iletmek, state ve dispatch'i uygulamanın derinliklerindeki herhangi bir bileşene kolayca erişilebilir kılar.
Performans Optimizasyonu ve Özel Hook'lar
React uygulamalarında performans, genellikle gereksiz yeniden render'ları önlemekle ilgilidir. Hooks, bu optimizasyonlar için güçlü araçlar sunar: useCallback, useMemo ve useRef. Ancak, bu hookların yanlış kullanımı performansı iyileştirmek yerine kötüleştirebilir. `useCallback` ve `useMemo`, referans eşitliğini (referential equality) stabilize etmek için kullanılır. Bir fonksiyonu veya hesaplama sonucunu, bağımlılıkları değişmediği sürece önbelleğe alırlar, böylece alt bileşenlere iletilen prop'lar gereksiz yere değişmez ve yeniden render tetiklenmez.
Özel Hook'lar (Custom Hooks), bileşen mantığını yeniden kullanılabilir fonksiyonlara çıkarmanın en etkili yoludur. Bir özel Hook, adı "use" ile başlayan sıradan bir JavaScript fonksiyonudur ve diğer Hook'ları çağırabilir. Bu, farklı bileşenlerde benzer stateful mantığı paylaşmanıza olanak tanır. Örneğin, bir `useLocalStorage` hook'u yazarak, hem state'i yönetebilir hem de değeri tarayıcının yerel depolamasına senkronize edebilirsiniz. Bu, mantığın kendisini değil, sadece state'i paylaşan yüksek seviyeli bir soyutlama sağlar.
Optimizasyon hook'larını kullanırken en sık yapılan hata, bunları her yere ve gereksiz yere uygulamaktır. `useCallback` veya `useMemo`, sadece referans eşitliğinin önemli olduğu durumlarda kullanılmalıdır: bir memoize edilmiş alt bileşene (`React.memo`) fonksiyon iletirken, bir efektin bağımlılık dizisinde kullanırken veya pahalı bir hesaplamayı önbelleğe alırken. Aksi takdirde, React'in dahili optimizasyon mekanizmalarına güvenmek ve kodunuzu basit tutmak genellikle daha iyidir. Her optimizasyonun bir maliyeti vardır (bellek, hesaplama).
| Optimizasyon Hook'u | Temel Amacı | Kullanım Senaryosu Örneği | Dikkat Edilmesi Gerekenler |
|---|---|---|---|
| useCallback | Fonksiyon referansını sabitlemek | `onClick`, `onSubmit` gibi callback'leri memoize edilmiş alt bileşene iletmek. | Bağımlılık dizisini doğru belirleyin; gereksiz kullanımdan kaçının. |
| useMemo | Pahalı hesaplama sonucunu önbelleğe almak | Büyük bir liste için filtrelenmiş/sıralanmış bir versiyon oluşturmak. | Hesaplamanın gerçekten "pahalı" olduğundan emin olun; aksi takdirde kullanmayın. |
| useRef | Render'lar arasında değişken saklamak, DOM'a erişmek | Bir input elementine odaklanmak, bir interval ID'sini saklamak. | `.current` değerini değiştirmek yeniden render tetiklemez. Sadece side-effect'lerde kullanın. |
| Özel Hook | Mantığı yeniden kullanılabilir hale getirmek | `useFetch`, `useForm`, `useEventListener` gibi soyutlamalar oluşturmak. | "use" önekiyle başlamalı, diğer Hook'ları içerebilir. Bileşen mantığını temizler. |
Özetle, performans optimizasyonu bir gereklilik haline geldiğinde, React Hook'ları doğru araçları sunar. Ancak, optimizasyonu ölçümlemeden yapmamak ve gereksiz karmaşıklıktan kaçınmak esastır. Özel Hook'lar ise, uygulamanızın kod tabanını daha modüler, okunabilir ve test edilebilir kılmak için en güçlü desenlerden birini sağlar. State mantığını, yan etkileri ve diğer Hook'ları kapsüllenmiş, yeniden kullanılabilir birimlere dönüştürürler.
useCallback, useMemo, useRef
React'in performans Hook'ları arasında yer alan `useCallback` ve `useMemo`, sıklıkla karıştırılır ancak farklı amaçlara hizmet ederler. useCallback, bir fonksiyonun referansını, bağımlılıkları değişmediği sürece sabit tutar. Bu, özellikle `React.memo` ile sarmalanmış alt bileşenlere fonksiyon prop'ları iletirken kritiktir. Eğer bir callback her üst bileşen render'ında yeniden oluşturulursa, alt bileşen prop'u farklı olduğu için gereksiz yere yeniden render edilir, bu da optimizasyonun etkisini ortadan kaldırır. `useCallback`, bu fonksiyonun kimliğini koruyarak bu sorunu çözer.
useMemo ise, pahalı hesaplamaların sonucunu önbelleğe alır. Bir fonksiyon ve bir bağımlılık dizisi alır; bağımlılıklar değişmediği sürece, önceki hesaplanmış değeri döndürür. Bu, render'lar arasında maliyetli işlemleri tekrarlamaktan kaçınmak için idealdir. Örneğin, büyük bir liste üzerinde filtreleme ve sıralama yapmak veya karmaşık bir matematiksel formül hesaplamak. Ancak, önemli bir uyarı: `useMemo` bir performans optimizasyonudur, bir semantik garanti değildir. React, gelecekte bazı önbelleğe alınmış değerleri "unutabilir", bu nedenle sadece performansı artırmak için kullanılmalı, mantıksal doğruluk için değil.
`useRef` Hook'u ise iki ana amaç için kullanılır: DOM elementlerine doğrudan erişim sağlamak ve render'lar arasında değişebilen, ancak değiştiğinde yeniden render tetiklemeyen değişkenler (mutable values) tutmak. Bir `ref` nesnesi oluşturur ve `.current` property'si ile bu değerlere erişim sağlarsınız. DOM erişimi için, ref'i bir JSX elementinin `ref` attribute'üne atarsınız. React, bileşen mount edildiğinde bu property'yi ilgili DOM node'u ile doldurur. Bu, focus yönetimi, metin seçimi veya üçüncü parti DOM kütüphaneleriyle entegrasyon için gereklidir.
Değişken saklama kullanımında, `useRef` bir sınıf örneği property'si gibi davranır. Örneğin, bir `setInterval`'in timer ID'sini, bir önceki prop veya state değerini veya bileşenin yaşam döngüsü boyunca kalması gereken herhangi bir değişkeni saklayabilirsiniz. `.current` değerini değiştirmek, bileşeni yeniden render ettirmez. Bu nedenle, `useRef`, `useState`'in tetiklediği render mekanizmasına bağlı olmayan, bileşenin "dış çantasında" bir şeyler taşımak için mükemmel bir araçtır. Ancak, bu değerlerin değişiminin render'a yol açmaması, kullanırken dikkatli olmayı gerektirir; UI bu değerlere bağlı olmamalıdır.
Bu üç Hook'un ortak bir özelliği, bağımlılık dizisi (dependency array) kullanmalarıdır. Bu dizinin doğru yönetilmesi, hem performans hem de doğru çalışma açısından hayati öneme sahiptir. Bağımlılık dizisine gereğinden fazla değişken eklemek, önbelleklemenin etkinliğini azaltır. Eksik bağımlılıklar ise, eski (stale) verilerin kullanılmasına ve zor bulunan bug'lara yol açabilir. ESLint kuralı `eslint-plugin-react-hooks`, bu bağımlılıkları otomatik olarak tespit etmek ve geliştiriciyi uyarmak için vazgeçilmez bir araçtır ve her projede etkinleştirilmelidir.
// useCallback ve useMemo Kullanım Örnekleri
import React, { useState, useCallback, useMemo } from 'react';
function MyComponent({ list }) {
const [filter, setFilter] = useState('');
// useCallback: Filtre değiştiğinde yeniden oluşturulan bir callback
const handleFilterChange = useCallback((event) => {
setFilter(event.target.value);
}, []); // Boş dizi: Bu fonksiyon bileşen ömrü boyunca aynı kalır.
// useMemo: Listeyi filtrelemek pahalı bir işlemse, sonucu önbelleğe al.
const filteredList = useMemo(() => {
console.log('Filtreleme hesaplanıyor...');
return list.filter(item => item.name.includes(filter));
}, [list, filter]); // 'list' veya 'filter' değiştiğinde yeniden hesapla.
// useRef: Bir input elementine odaklanmak için
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} value={filter} onChange={handleFilterChange} />
<button onClick={focusInput}>Fokusla</button>
<ul>
{filteredList.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
Sonuç olarak, `useCallback`, `useMemo` ve `useRef`, React uygulamalarında ince ayar yapmak için güçlü araçlardır. useCallback ve useMemo'yu sadece kanıtlanmış bir performans sorununu çözmek için kullanın. Erken optimizasyondan kaçının, çünkü bu hook'ların kendisi de bellek ve hesaplama yükü getirir. `useRef` ise, React'in declarative modelinden tactical olarak "kaçmanız" gereken, DOM manipülasyonu veya imperatif etkileşimler gibi nadir senaryolar için tasarlanmıştır. Bu araçları anlamak ve doğru bağlamda uygulamak, hem performanslı hem de sürdürülebilir kod yazmanın anahtarıdır.