Türkçe

Karmaşık uygulama durumlarını etkin bir şekilde yönetmek, global React projeleri için performansı ve sürdürülebilirliği artırmak amacıyla React'in useReducer kancasına derinlemesine dalın.

React useReducer Deseni: Karmaşık Durum Yönetiminde Ustalaşma

Sürekli gelişen ön uç (front-end) geliştirme dünyasında React, kullanıcı arayüzleri oluşturmak için lider bir çerçeve olarak kendini kanıtlamıştır. Uygulamalar karmaşıklaştıkça, durum (state) yönetimi giderek daha zorlu hale gelir. useState kancası, bir bileşen içindeki durumu yönetmek için basit bir yol sunar, ancak daha karmaşık senaryolar için React güçlü bir alternatif sunar: useReducer kancası. Bu blog yazısı, useReducer desenini derinlemesine inceliyor, faydalarını, pratik uygulamalarını ve React uygulamalarınızı global ölçekte nasıl önemli ölçüde geliştirebileceğini araştırıyor.

Karmaşık Durum Yönetimi İhtiyacını Anlamak

React uygulamaları geliştirirken, bir bileşenin durumunun yalnızca basit bir değer olmadığı, bunun yerine birbiriyle bağlantılı veri noktaları topluluğu veya önceki durum değerlerine bağlı bir durum olduğu durumlarla sık sık karşılaşırız. Şu örnekleri düşünün:

Bu senaryolarda, yalnızca useState kullanmak, karmaşık ve yönetilmesi zor bir koda yol açabilir. Tek bir olaya yanıt olarak birden fazla durum değişkenini güncellemek zahmetli hale gelebilir ve bu güncellemeleri yönetme mantığı bileşen boyunca dağılarak anlaşılmasını ve sürdürülmesini zorlaştırabilir. İşte bu noktada useReducer parlar.

useReducer Kancasına Giriş

useReducer kancası, karmaşık durum mantığını yönetmek için useState'e bir alternatiftir. Redux deseninin prensiplerine dayanır, ancak React bileşeninin kendi içinde uygulanır, bu da birçok durumda ayrı bir harici kütüphane ihtiyacını ortadan kaldırır. Durum güncelleme mantığınızı reducer adı verilen tek bir fonksiyonda merkezileştirmenize olanak tanır.

useReducer kancası iki argüman alır:

Bu kanca, iki öğe içeren bir dizi döndürür:

Reducer Fonksiyonu

Reducer fonksiyonu, useReducer deseninin kalbidir. Saf bir fonksiyondur, yani herhangi bir yan etkisi olmamalı (API çağrıları yapmak veya global değişkenleri değiştirmek gibi) ve aynı girdi için her zaman aynı çıktıyı döndürmelidir. Reducer fonksiyonu iki argüman alır:

Reducer fonksiyonunun içinde, farklı eylem türlerini işlemek ve durumu buna göre güncellemek için bir switch ifadesi veya if/else if ifadeleri kullanırsınız. Bu, durum güncelleme mantığınızı merkezileştirir ve durumun farklı olaylara yanıt olarak nasıl değiştiğini anlamayı kolaylaştırır.

Dispatch Fonksiyonu

Dispatch fonksiyonu, durum güncellemelerini tetiklemek için kullandığınız yöntemdir. dispatch(action)'ı çağırdığınızda, eylem reducer fonksiyonuna iletilir ve bu fonksiyon da eylemin türüne ve payload'una göre durumu günceller.

Pratik Bir Örnek: Sayaç Uygulaması

Basit bir örnekle başlayalım: bir sayaç bileşeni. Bu, daha karmaşık örneklere geçmeden önce temel kavramları göstermektedir. Artırabilen, azaltabilen ve sıfırlayabilen bir sayaç oluşturacağız:


import React, { useReducer } from 'react';

// Eylem türlerini tanımla
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const RESET = 'RESET';

// Reducer fonksiyonunu tanımla
function counterReducer(state, action) {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    case RESET:
      return { count: 0 };
    default:
      return state;
  }
}

function Counter() {
  // useReducer'ı başlat
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>Sayı: {state.count}</p>
      <button onClick={() => dispatch({ type: INCREMENT })}>Artır</button>
      <button onClick={() => dispatch({ type: DECREMENT })}>Azalt</button>
      <button onClick={() => dispatch({ type: RESET })}>Sıfırla</button>
    </div>
  );
}

export default Counter;

Bu örnekte:

Sayaç Örneğini Genişletme: Payload Ekleme

Sayacı belirli bir değer kadar artırmaya izin verecek şekilde değiştirelim. Bu, bir eylemde payload kavramını tanıtır:


import React, { useReducer } from 'react';

const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const RESET = 'RESET';
const SET_VALUE = 'SET_VALUE';

function counterReducer(state, action) {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + action.payload };
    case DECREMENT:
      return { count: state.count - action.payload };
    case RESET:
      return { count: 0 };
    case SET_VALUE:
      return { count: action.payload };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });
  const [inputValue, setInputValue] = React.useState(1);

  return (
    <div>
      <p>Sayı: {state.count}</p>
      <button onClick={() => dispatch({ type: INCREMENT, payload: parseInt(inputValue) || 1 })}>{inputValue} kadar Artır</button>
      <button onClick={() => dispatch({ type: DECREMENT, payload: parseInt(inputValue) || 1 })}>{inputValue} kadar Azalt</button>
      <button onClick={() => dispatch({ type: RESET })}>Sıfırla</button>
       <input
         type="number"
         value={inputValue}
         onChange={(e) => setInputValue(e.target.value)}
       />
      </div>
  );
}

export default Counter;

Bu genişletilmiş örnekte:

useReducer Kullanmanın Avantajları

useReducer deseni, karmaşık durum yönetimi için doğrudan useState kullanmaya göre çeşitli avantajlar sunar:

useReducer Ne Zaman Kullanılmalı

useReducer önemli avantajlar sunsa da her zaman doğru seçim değildir. Şu durumlarda useReducer kullanmayı düşünün:

Basit durum güncellemeleri için useState genellikle yeterli ve kullanımı daha basittir. Karar verirken durumunuzun karmaşıklığını ve büyüme potansiyelini göz önünde bulundurun.

İleri Düzey Kavramlar ve Teknikler

useReducer'ı Context ile Birleştirmek

Global durumu yönetmek veya durumu birden çok bileşen arasında paylaşmak için useReducer'ı React'in Context API'si ile birleştirebilirsiniz. Bu yaklaşım, ekstra bağımlılıklar eklemek istemediğiniz küçük ve orta ölçekli projeler için genellikle Redux'a tercih edilir.


import React, { createContext, useReducer, useContext } from 'react';

// Eylem türlerini ve reducer'ı tanımla (önceki gibi)
const INCREMENT = 'INCREMENT';
// ... (diğer eylem türleri ve counterReducer fonksiyonu)

const CounterContext = createContext();

function CounterProvider({ children }) {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <CounterContext.Provider value={{ state, dispatch }}>
      {children}
    </CounterContext.Provider>
  );
}

function useCounter() {
  return useContext(CounterContext);
}

function Counter() {
  const { state, dispatch } = useCounter();

  return (
    <div>
      <p>Sayı: {state.count}</p>
      <button onClick={() => dispatch({ type: INCREMENT })}>Artır</button>
    </div>
  );
}

function App() {
  return (
    <CounterProvider>
      <Counter />
    </CounterProvider>
  );
}

export default App;

Bu örnekte:

useReducer'ı Test Etme

Reducer'ları test etmek basittir çünkü saf fonksiyonlardır. Jest veya Mocha gibi bir birim test çerçevesi kullanarak reducer fonksiyonunu tek başına kolayca test edebilirsiniz. İşte Jest kullanarak bir örnek:


import { counterReducer } from './counterReducer'; // counterReducer'ın ayrı bir dosyada olduğunu varsayarak

const INCREMENT = 'INCREMENT';

describe('counterReducer', () => {
  it('sayıyı artırmalıdır', () => {
    const state = { count: 0 };
    const action = { type: INCREMENT };
    const newState = counterReducer(state, action);
    expect(newState.count).toBe(1);
  });

   it('bilinmeyen eylem türleri için aynı durumu döndürmelidir', () => {
        const state = { count: 10 };
        const action = { type: 'UNKNOWN_ACTION' };
        const newState = counterReducer(state, action);
        expect(newState).toBe(state); // Durumun değişmediğini doğrula
    });
});

Reducer'larınızı test etmek, beklendiği gibi davrandıklarından emin olmanızı sağlar ve durum mantığınızı yeniden düzenlemeyi (refactoring) kolaylaştırır. Bu, sağlam ve sürdürülebilir uygulamalar oluşturmada kritik bir adımdır.

Memoization ile Performansı Optimize Etme

Karmaşık durumlar ve sık güncellemelerle çalışırken, bileşenlerinizin performansını optimize etmek için useMemo kullanmayı düşünün, özellikle de duruma dayalı olarak hesaplanan türetilmiş değerleriniz varsa. Örneğin:


import React, { useReducer, useMemo } from 'react';

function reducer(state, action) {
  // ... (reducer mantığı) 
}

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, initialState);

  // Türetilmiş bir değer hesapla, useMemo ile hafızaya al
  const derivedValue = useMemo(() => {
    // Duruma dayalı pahalı hesaplama
    return state.value1 + state.value2;
  }, [state.value1, state.value2]); // Bağımlılıklar: sadece bu değerler değiştiğinde yeniden hesapla

  return (
    <div>
      <p>Türetilmiş Değer: {derivedValue}</p>
      <button onClick={() => dispatch({ type: 'UPDATE_VALUE1', payload: 10 })}>Değer 1'i Güncelle</button>
      <button onClick={() => dispatch({ type: 'UPDATE_VALUE2', payload: 20 })}>Değer 2'yi Güncelle</button>
    </div>
  );
}

Bu örnekte, derivedValue yalnızca state.value1 veya state.value2 değiştiğinde hesaplanır, böylece her yeniden render işleminde gereksiz hesaplamalar önlenir. Bu yaklaşım, optimum render performansı sağlamak için yaygın bir uygulamadır.

Gerçek Dünya Örnekleri ve Kullanım Senaryoları

Global bir kitle için React uygulamaları oluştururken useReducer'ın değerli bir araç olduğu birkaç pratik örneği inceleyelim. Bu örneklerin temel kavramları göstermek için basitleştirildiğini unutmayın. Gerçek uygulamalar daha karmaşık mantık ve bağımlılıklar içerebilir.

1. E-ticaret Ürün Filtreleri

Geniş bir ürün kataloğuna sahip bir e-ticaret web sitesi (Amazon veya AliExpress gibi global olarak mevcut olan popüler platformları düşünün) hayal edin. Kullanıcıların ürünleri çeşitli kriterlere (fiyat aralığı, marka, boyut, renk, menşe ülke vb.) göre filtrelemesi gerekir. useReducer, filtre durumunu yönetmek için idealdir.


import React, { useReducer } from 'react';

const initialState = {
  priceRange: { min: 0, max: 1000 },
  brand: [], // Seçilen markaların dizisi
  color: [], // Seçilen renklerin dizisi
  //... diğer filtre kriterleri
};

function filterReducer(state, action) {
  switch (action.type) {
    case 'UPDATE_PRICE_RANGE':
      return { ...state, priceRange: action.payload };
    case 'TOGGLE_BRAND':
      const brand = action.payload;
      return { ...state, brand: state.brand.includes(brand) ? state.brand.filter(b => b !== brand) : [...state.brand, brand] };
    case 'TOGGLE_COLOR':
      // Renk filtreleme için benzer mantık
      return { ...state, color: state.color.includes(action.payload) ? state.color.filter(c => c !== action.payload) : [...state.color, action.payload] };
    // ... diğer filtre eylemleri
    default:
      return state;
  }
}

function ProductFilter() {
  const [state, dispatch] = useReducer(filterReducer, initialState);

  // Filtre kriterlerini seçmek ve dispatch eylemlerini tetiklemek için UI bileşenleri
  // Örneğin: Fiyat için aralık girdisi, markalar için onay kutuları vb.

  return (
    <div>
      <!-- Filtre UI elemanları -->
    </div>
  );
}

Bu örnek, birden çok filtre kriterinin kontrollü bir şekilde nasıl yönetileceğini gösterir. Bir kullanıcı herhangi bir filtre ayarını (fiyat, marka vb.) değiştirdiğinde, reducer filtre durumunu buna göre günceller. Ürünleri görüntülemekten sorumlu bileşen, daha sonra görüntülenen ürünleri filtrelemek için güncellenmiş durumu kullanır. Bu desen, global e-ticaret platformlarında yaygın olan karmaşık filtreleme sistemleri oluşturmayı destekler.

2. Çok Adımlı Formlar (örn. Uluslararası Gönderim Formları)

Birçok uygulama, uluslararası gönderim veya karmaşık gereksinimlere sahip kullanıcı hesapları oluşturmak için kullanılanlar gibi çok adımlı formlar içerir. useReducer, bu tür formların durumunu yönetmede mükemmeldir.


import React, { useReducer } from 'react';

const initialState = {
  step: 1, // Formdaki mevcut adım
  formData: {
    firstName: '',
    lastName: '',
    address: '',
    city: '',
    country: '',
    // ... diğer form alanları
  },
  errors: {},
};

function formReducer(state, action) {
  switch (action.type) {
    case 'NEXT_STEP':
      return { ...state, step: state.step + 1 };
    case 'PREV_STEP':
      return { ...state, step: state.step - 1 };
    case 'UPDATE_FIELD':
      return { ...state, formData: { ...state.formData, [action.payload.field]: action.payload.value } };
    case 'SET_ERRORS':
      return { ...state, errors: action.payload };
    case 'SUBMIT_FORM':
      // Form gönderme mantığını burada ele al, örn. API çağrıları
      return state;
    default:
      return state;
  }
}

function MultiStepForm() {
  const [state, dispatch] = useReducer(formReducer, initialState);

  // Formun her adımı için render mantığı
  // Durumdaki mevcut adıma göre
  const renderStep = () => {
    switch (state.step) {
      case 1:
        return <Step1 formData={state.formData} dispatch={dispatch} />;
      case 2:
        return <Step2 formData={state.formData} dispatch={dispatch} />;
      // ... diğer adımlar
      default:
        return <p>Geçersiz Adım</p>;
    }
  };

  return (
    <div>
      {renderStep()}
      <!-- Mevcut adıma göre gezinme düğmeleri (İleri, Geri, Gönder) -->
    </div>
  );
}

Bu, farklı form alanlarını, adımları ve olası doğrulama hatalarını yapılandırılmış ve sürdürülebilir bir şekilde nasıl yöneteceğinizi gösterir. Özellikle yerel geleneklerine ve Facebook veya WeChat gibi çeşitli platformlardaki deneyimlerine göre farklı beklentilere sahip olabilecek uluslararası kullanıcılar için kullanıcı dostu kayıt veya ödeme süreçleri oluşturmak için kritik öneme sahiptir.

3. Gerçek Zamanlı Uygulamalar (Sohbet, İşbirliği Araçları)

useReducer, Google Docs gibi işbirliği araçları veya mesajlaşma uygulamaları gibi gerçek zamanlı uygulamalar için faydalıdır. Mesaj alma, kullanıcı katılması/ayrılması ve bağlantı durumu gibi olayları yöneterek kullanıcı arayüzünün gerektiği gibi güncellenmesini sağlar.


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

const initialState = {
  messages: [],
  users: [],
  connectionStatus: 'connecting',
};

function chatReducer(state, action) {
  switch (action.type) {
    case 'RECEIVE_MESSAGE':
      return { ...state, messages: [...state.messages, action.payload] };
    case 'USER_JOINED':
      return { ...state, users: [...state.users, action.payload] };
    case 'USER_LEFT':
      return { ...state, users: state.users.filter(user => user.id !== action.payload.id) };
    case 'SET_CONNECTION_STATUS':
      return { ...state, connectionStatus: action.payload };
    default:
      return state;
  }
}

function ChatRoom() {
  const [state, dispatch] = useReducer(chatReducer, initialState);

  useEffect(() => {
    // WebSocket bağlantısı kur (örnek):
    const socket = new WebSocket('wss://your-websocket-server.com');

    socket.onopen = () => dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'connected' });
    socket.onmessage = (event) => dispatch({ type: 'RECEIVE_MESSAGE', payload: JSON.parse(event.data) });
    socket.onclose = () => dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'disconnected' });

    return () => socket.close(); // Bileşen kaldırıldığında temizle
  }, []);

  // Duruma göre mesajları, kullanıcı listesini ve bağlantı durumunu render et
  return (
    <div>
      <p>Bağlantı Durumu: {state.connectionStatus}</p>
      <!-- Mesajları, kullanıcı listesini görüntülemek ve mesaj göndermek için UI -->
    </div>
  );
}

Bu örnek, gerçek zamanlı bir sohbeti yönetmek için temel oluşturur. Durum, mesaj depolamayı, sohbetteki mevcut kullanıcıları ve bağlantı durumunu yönetir. useEffect kancası, WebSocket bağlantısını kurmaktan ve gelen mesajları işlemekten sorumludur. Bu yaklaşım, dünya çapındaki kullanıcılara hitap eden duyarlı ve dinamik bir kullanıcı arayüzü oluşturur.

useReducer Kullanımı için En İyi Uygulamalar

useReducer'ı etkili bir şekilde kullanmak ve sürdürülebilir uygulamalar oluşturmak için şu en iyi uygulamaları göz önünde bulundurun:

Sonuç

useReducer kancası, React uygulamalarında karmaşık durumu yönetmek için güçlü ve çok yönlü bir araçtır. Merkezileştirilmiş durum mantığı, geliştirilmiş kod organizasyonu ve artırılmış test edilebilirlik gibi sayısız avantaj sunar. En iyi uygulamaları takip ederek ve temel kavramlarını anlayarak, daha sağlam, sürdürülebilir ve performanslı React uygulamaları oluşturmak için useReducer'dan yararlanabilirsiniz. Bu desen, karmaşık durum yönetimi zorluklarının üstesinden gelmenizi etkili bir şekilde sağlayarak, dünya çapında sorunsuz kullanıcı deneyimleri sunan global kullanıma hazır uygulamalar oluşturmanıza olanak tanır.

React geliştirmeye daha derinlemesine daldıkça, useReducer desenini araç setinize dahil etmek, şüphesiz daha temiz, daha ölçeklenebilir ve kolayca sürdürülebilir kod tabanlarına yol açacaktır. Her zaman uygulamanızın özel ihtiyaçlarını göz önünde bulundurmayı ve her durum için en iyi durum yönetimi yaklaşımını seçmeyi unutmayın. Mutlu kodlamalar!