React'in useReducer Hook'u, Flux ve Redux mimarilerinden esinlenen, bileşen durumunu yönetmek için güçlü bir alternatiftir. useState Hook'unun aksine, durum geçişleri daha karmaşık ve birbirine bağlı olduğunda tercih edilir. useReducer, durumu ve bu durumu değiştiren mantığı merkezileştirir, böylece bileşen mantığı daha öngörülebilir ve test edilebilir hale gelir.

Yapının anatomisi üç temel unsurdan oluşur: state (durum), action (eylem) ve reducer (indirgeyici). State, uygulamanın anlık görüntüsünü temsil eder. Action, durumda ne tür bir değişiklik yapılacağını tanımlayan bir nesnedir ve genellikle bir type alanı ile isteğe bağlı bir payload içerir. Reducer ise saf bir fonksiyondur; mevcut state ve bir action'ı alır ve yeni bir state döndürür.

Parametre Tipi Açıklama
state Herhangi bir tip (Object, Array, vb.) Mevcut durumun anlık değeri.
action Object { type: 'ACTION_TYPE', payload: ... } şeklinde durum değişikliğini tanımlayan nesne.
reducer Function (state, action) => newState imzasına sahip saf fonksiyon.

Reducer fonksiyonunun saflığı kritik öneme sahiptir; aynı girdi için her zaman aynı çıktıyı üretmelidir. Bu, yan etkilerin (side effects) olmaması, API çağrıları veya geçersiz kılma (mutasyon) yapılmaması gerektiği anlamına gelir. Bu kısıtlama, durum geçişlerinin deterministik olmasını sağlar ve hata ayıklamayı kolaylaştırır.

useState ile Karşılaştırma

useState ve useReducer arasındaki temel felsefi fark, durum güncelleme mantığının nerede bulunduğudur. useState, durumu ve onu güncelleyen fonksiyonu bileşenin içinde dağınık bir şekilde tutarken, useReducer bu mantığı merkezi bir reducer fonksiyonuna taşır. Bu, özellikle bir sonraki durumun bir öncekine bağlı olduğu durumlarda (setCount(prev => prev + 1) gibi) veya birden fazla alt değerin birbiriyle senkronize değiştiği durumlarda belirgin bir avantaj sağlar.

Kullanım alanları açısından bakıldığında, useState basit, yerel ve birbirinden bağımsız durum parçaları için idealdir. Örneğin, bir input alanının değeri veya bir toggle butonunun açık/kapalı durumu. Buna karşılık, useReducer, form state'leri, çok adımlı wizard'lar, karmaşık modal yönetimi veya küresel durum yönetimi için Context API ile birlikte kullanıldığında daha uygundur. Karmaşıklık arttıkça, reducer'ın sunduğu yapı, kodun sürdürülebilirliğini artırır.

Kriter useState useReducer
Durum Mantığı Bileşen içinde dağınık (In-line) Merkezi, tek bir reducer fonksiyonunda
Okunabilirlik Küçük ve basit durumlar için yüksek Karmaşık durum geçişleri için yüksek
Test Edilebilirlik Düşük (Bileşene bağımlı) Yüksek (Saf reducer fonksiyonu)
Performans Optimizasyonu useCallback gerektirebilir Dispatch fonksiyonu sabittir, bağımlılık dizisi gereksinimini azaltır

Performans açısından, useReducer'ın bir avantajı, dispatch fonksiyonunun render'lar arasında sabit kalmasıdır. Bu, alt bileşenlere callback'leri iletirken (prop drilling) gereksiz yeniden render'ları önlemek için useCallback kullanma ihtiycını büyük ölçüde ortadan kaldırır. Ayrıca, karmaşık güncelleme mantığı, bileşen gövdesinden ayrıldığı için, React'in memoization tekniklerini (React.memo, useMemo) uygulamak daha kolay hale gelir.

Karmaşık Durum Yönetimi için Uygulamalar

useReducer'ın gerçek gücü, birbirine sıkı sıkıya bağlı durum parçalarının yönetiminde ortaya çıkar. Örneğin, çok adımlı bir form veya bir veri tablosunda filtreleme, sıralama ve sayfalama işlemlerinin aynı anda yönetilmesi gerektiğinde, useState ile yönetim hızla içinden çıkılmaz bir hale gelebilir. Bu senaryolarda, reducer, tüm ilgili durumu tek bir nesnede kapsayarak ve her bir etkileşimi net bir action tipi ile tanımlayarak tutarlılığı ve öngörülebilirliği garanti eder.

Context API ile entegrasyon, useReducer'ın bir diğer önemli uygulama alanıdır. Küresel durum yönetimi için Redux gibi kütüphanelere bağımlılık olmadan, bir durum ve onun dispatch fonksiyonu tüm uygulama ağacına sağlanabilir. Bu model, prop drilling sorununu çözerken, durum güncelleme mantığının yine de merkezi ve test edilebilir kalmasını sağlar. Bu yaklaşım, orta ölçekli uygulamalarda genellikle yeterli olmakta ve tooling karmaşıklığını azaltmaktadır.


// Context ve useReducer Entegrasyon Örneği
import React, { useReducer, createContext, useContext } from 'react';

const initialState = { theme: 'light', user: null, notifications: [] };

function appReducer(state, action) {
  switch (action.type) {
    case 'SET_THEME':
      return { ...state, theme: action.payload };
    case 'LOGIN':
      return { ...state, user: action.payload };
    case 'ADD_NOTIFICATION':
      return { ...state, notifications: [...state.notifications, action.payload] };
    default:
      return state;
  }
}

const AppStateContext = createContext();
const AppDispatchContext = createContext();

export function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState);
  return (
    
      
        {children}
      
    
  );
}

export const useAppState = () => useContext(AppStateContext);
export const useAppDispatch = () => useContext(AppDispatchContext);

Side effect yönetimi, useReducer kullanırken dikkat edilmesi gereken kritik bir konudur. Reducer fonksiyonları saf olmalıdır, bu nedenle API çağrıları, zamanlayıcılar (timers) veya diğer yan etkiler reducer içinde asla gerçekleştirilmemelidir. Bunun yerine, yan etkiler, useEffect Hook'u içinde, dispatch fonksiyonu tetiklenerek yönetilmelidir. Bu model, etkilerin (effects) ve durum geçişlerinin ayrılmasını sağlayarak, daha temiz ve hata ayıklaması daha kolay bir kod tabanı oluşturur.

  • Form State Yönetimi: Çoklu alan, doğrulama (validation) ve gönderim (submission) durumlarının tek bir reducer ile koordine edilmesi.
  • Karmaşık UI State'i: Bir modalın açık/kapalı olması, yükleniyor durumu ve içeriğinin senkronize yönetimi.
  • Geçmiş ve Geri Al/Yinele (Undo/Redo): Durum değişikliklerinin bir dizi (array) olarak kaydedilmesi ve belirli action'lar ile gezinilmesi.
  • WebSocket veya Real-time Data: Gelen mesajların veya veri paketlerinin mevcut duruma göre işlenip birleştirilmesi (merging).

Ayrıca, useReducer ile lazy initialization (tembel başlatma) da mümkündür. İlk state'i hesaplamak maliyetli bir işlem gerektiriyorsa, `useReducer`'a bir başlangıç state'i yerine, ilk argüman olarak bir `init` fonksiyonu geçilebilir. Bu fonksiyon, başlangıç state'ini döndrür ve yalnızca ilk render sırasında çalıştırılır, bu da performans optimizasyonu sağlar.

Sonuç

useReducer'ı etkin bir şekilde kullanmak için belirli kalıplara uymak önemlidir. Action tiplerini sabit (constant) değişkenler olarak tanımlamak, yazım hatalarını önler ve IDE tamamlama desteğini artırır. Reducer fonksiyonu, genellikle bir `switch` ifadesi ile yazılır ve her dalında mevcut state'i asla doğrudan değiştirmemek (mutate etmemek), bunun yerine her seferinde yeni bir nesne veya dizi döndürmek esastır.

Performans optimizasyonu açısından, dispatch edilen action nesnelerinin mümkün olduğunca hafif ve serializable olmasına dikkat edilmelidir. Büyük nesneleri payload olarak iletmekten kaçınılmalı, gerekli minimum bilgi iletilmelidir. Eğer bir alt bileşen yalnızca dispatch fonksiyonuna ihtiyaç duyuyorsa, bu durum state değişikliklerinden etkilenmeyeceği için gereksiz yeniden render'lar otomatik olarak önlenmiş olur.

Karmaşık reducer'ların test edilmesi, saf fonksiyonlar oldukları için son derece basittir. Birim testleri (unit tests), farklı action'lar ve başlangıç state'leri için beklenen çıktıyı doğrulayabilir. Bu, uygulamanın durum yönetimi mantığının güvenilirliğini sağlamada kritik bir adımdır ve useReducer'ın, useState'e kıyasla sağladığı en büyük avantajlardan biridir.