Takip et

React useReducer Derin Dalış: Quiz’den Kıdemli Desenlere

React uygulamalarınızda karmaşık durum yönetimi mi yapmaya çalışıyorsunuz? Belki de useState hook’unun yetersiz kaldığı durumlarla karşılaşıyorsunuzdur. Bu makale, useReducer‘ın temel prensiplerinden ileri düzey kullanım kalıplarına kadar her şeyi adım adım açıklayarak, sizi bir başlangıç seviyesinden kıdemli bir React geliştirici seviyesine taşıyacak.

React’ın temel yapı taşlarından biri olan state (durum) yönetimi, uygulamaların dinamik ve etkileşimli olmasını sağlayan kritik bir unsurdur. Küçük ölçekli uygulamalarda useState hook’u genellikle yeterli olurken, uygulamalar büyüdükçe ve state mantığı daha karmaşık hale geldikçe, useState kullanımı bazı zorlukları beraberinde getirebilir. Özellikle bir state güncellemesi başka bir state’e bağlı olduğunda veya birçok state güncellemesinin aynı anda gerçekleşmesi gerektiğinde, kodun okunabilirliği ve bakımı zorlaşabilir. İşte tam da bu noktada useReducer devreye girer; useState‘e göre daha yapılandırılmış ve öngörülebilir bir state yönetimi çözümü sunar.

useReducer, adından da anlaşılacağı gibi, JavaScript’teki Array.prototype.reduce() metodunun çalışma prensibine benzer bir mantıkla işler. Bir reducer fonksiyonu, mevcut state’i ve bir action‘ı alarak yeni bir state döndürür. Bu model, özellikle Redux gibi kütüphanelerde yaygın olarak kullanılan “tek kaynak doğruluk” prensibine dayanır ve state değişimlerini merkezi bir mantıkla yönetmeyi kolaylaştırır. Böylece, uygulamanın herhangi bir yerinden bir action gönderildiğinde, state’in nasıl değişeceği tek bir yerde tanımlanmış olur, bu da hataların izlenmesini ve düzeltilmesini önemli ölçüde basitleştirir. Ayrıca, state güncellemelerinin ardışık olduğu veya önceki state’e bağlı olduğu durumlarda useReducer, useState‘in callback formlarına kıyasla daha temiz ve anlaşılır bir kod yapısı sunar. Dolayısıyla, state mantığınızın karmaşıklığı arttığında veya birden fazla alt bileşenin aynı state’i paylaşması gerektiğinde, useReducer kullanmak, uygulamanızın ölçeklenebilirliğini ve sürdürülebilirliğini artıracaktır.

useReducer Temelleri: State, Action ve Reducer Nasıl Çalışır?

useReducer‘ı anlamak için üç temel kavramı netleştirmemiz gerekiyor: state, action ve reducer. Bu üçlü, useReducer hook’unun kalbini oluşturur ve birlikte uyumlu bir şekilde çalışarak uygulamanızın durumunu yönetir. Hadi her birini ayrı ayrı inceleyelim ve basit bir sayaç örneği üzerinden nasıl işlediklerini görelim.

1. State (Durum): Uygulamanızın mevcut anlık veri yığınıdır. Bir kullanıcının adı, bir ürünün sepet miktarı, bir görevin tamamlanıp tamamlanmadığı gibi dinamik bilgiler state içinde tutulur. useReducer bağlamında, state genellikle bir JavaScript nesnesi veya basit bir değer olabilir. Örneğin, bir sayaç uygulaması için state basitçe bir sayı olabilir: { count: 0 }.

2. Action (Eylem): State’i değiştirmek için gönderilen bir sinyaldir. Action’lar genellikle bir type özelliği içeren basit JavaScript nesneleridir ve isteğe bağlı olarak bir payload (yük) taşıyabilirler. type, hangi değişikliğin yapılacağını belirtirken, payload ise bu değişiklik için gerekli veriyi taşır. Örneğin, bir sayaç için artırma action’ı { type: 'INCREMENT' } şeklinde olabilirken, belirli bir değerle artırma action’ı { type: 'INCREMENT_BY', payload: 5 } şeklinde olabilir. Action’lar, uygulamanızda neyin gerçekleştiğini tanımlayan soyut mesajlar gibidir.

3. Reducer (İndirgeyici): Mevcut state’i ve gönderilen action’ı alarak, bu bilgilere göre yeni bir state döndüren saf bir fonksiyondur. Saf fonksiyon olması demek, aynı input’larla her zaman aynı output’u vermesi ve dışarıdan hiçbir side effect (yan etki) üretmemesi anlamına gelir. Reducer fonksiyonu genellikle bir switch ifadesi kullanılarak farklı action türlerine göre farklı state değişimlerini yönetir. Reducer fonksiyonu asla mevcut state’i doğrudan değiştirmez; bunun yerine, her zaman yeni bir state nesnesi döndürür. Bu prensip, React’ın ve immutability (değişmezlik) kavramının temelini oluşturur ve state yönetimini çok daha öngörülebilir hale getirir.

Aşağıdaki örnekte, basit bir sayaç uygulaması için bu üç bileşenin nasıl bir araya geldiğini görebilirsiniz:


    // 1. Reducer Fonksiyonu
    const 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 };
        case 'INCREMENT_BY_AMOUNT':
          return { count: state.count + action.payload };
        default:
          return state; // Bilinmeyen action'lar için mevcut state'i döndür
      }
    };

    // 2. React Bileşeni içinde useReducer kullanımı
    const Counter = () => {
      // useReducer hook'u: [state, dispatch] döndürür
      // İlk argüman: Reducer fonksiyonu
      // İkinci argüman: Başlangıç state'i
      const [state, dispatch] = React.useReducer(counterReducer, { count: 0 });

      return (
        

Sayaç: {state.count}

); }; ReactDOM.render(, document.getElementById('root'));

Yukarıdaki örnekte counterReducer fonksiyonu, state ve action'a göre state'i nasıl güncelleyeceğini belirler. Counter bileşeni içinde ise useReducer hook'unu kullanarak state'i ve state'i güncellemek için kullanılan dispatch fonksiyonunu elde ederiz. Her bir butona tıklandığında, ilgili action dispatch edilerek reducer'a gönderilir ve state güncellenir. Bu, state yönetimini merkezi ve öngörülebilir bir şekilde yapmamızı sağlar, özellikle de uygulamamızda birden fazla state değişikliği gerektiğinde bu yaklaşımın faydaları belirginleşir.

Gerçek Dünya Senaryosu: Bir Görev Yönetim Uygulaması ile useReducer

Basit bir sayaç örneği, useReducer'ın temelini anlamak için harika olsa da, gerçek dünya uygulamalarında karşılaşılan karmaşıklıklar genellikle çok daha fazladır. Şimdi, bir görev (todo) yönetim uygulaması geliştirerek useReducer'ın daha karmaşık state yapılarını ve birden fazla action türünü nasıl yöneteceğini derinlemesine inceleyelim. Bu vaka analizi, görev ekleme, silme, tamamlama ve filtreleme gibi yaygın işlevleri kapsayacak ve useReducer'ın neden büyük uygulamalarda tercih edildiğini gözler önüne serecek.

Hayal edelim ki kullanıcıların görevlerini listeleyebileceği, yeni görevler ekleyebileceği, mevcut görevleri tamamlandı olarak işaretleyebileceği ve hatta belirli kriterlere göre görevleri filtreleyebileceği bir uygulama yapıyoruz. Bu uygulama için state'imiz sadece bir sayı değil, görev nesnelerinden oluşan bir dizi ve filtre ayarlarını içerecektir. İlk olarak, başlangıç state'imizi ve reducer fonksiyonumuzu tanımlayarak başlayalım.


    // Başlangıç State'i
    const initialTodosState = {
      todos: [],
      filter: 'all', // 'all', 'active', 'completed'
      nextId: 1, // Yeni görevlere benzersiz ID atamak için
    };

    // Reducer Fonksiyonu
    const todosReducer = (state, action) => {
      switch (action.type) {
        case 'ADD_TODO':
          return {
            ...state,
            todos: [
              ...state.todos,
              {
                id: state.nextId,
                text: action.payload.text,
                completed: false,
              },
            ],
            nextId: state.nextId + 1,
          };
        case 'TOGGLE_TODO':
          return {
            ...state,
            todos: state.todos.map((todo) =>
              todo.id === action.payload.id
                ? { ...todo, completed: !todo.completed }
                : todo
            ),
          };
        case 'REMOVE_TODO':
          return {
            ...state,
            todos: state.todos.filter((todo) => todo.id !== action.payload.id),
          };
        case 'SET_FILTER':
          return {
            ...state,
            filter: action.payload.filter,
          };
        default:
          return state;
      }
    };
  

Yukarıdaki todosReducer fonksiyonunda, farklı action türlerine (ADD_TODO, TOGGLE_TODO, REMOVE_TODO, SET_FILTER) göre state'in nasıl değiştiğini görüyoruz. Her durumda, reducer mevcut state'i asla doğrudan değiştirmiyor; bunun yerine, immutability prensibine uygun olarak her zaman yeni bir state nesnesi döndürüyor. Özellikle ADD_TODO durumunda, yeni bir görev eklenirken nextId'nin de güncellenmesi, state'in tutarlılığını sağlamak için önemlidir.

Şimdi bu reducer'ı kullanan bir React bileşeni oluşturalım. Bu bileşen, kullanıcı arayüzü ile reducer arasında köprü görevi görecek.


    const TodoApp = () => {
      const [state, dispatch] = React.useReducer(todosReducer, initialTodosState);
      const [newTodoText, setNewTodoText] = React.useState('');

      const filteredTodos = state.todos.filter((todo) => {
        if (state.filter === 'active') return !todo.completed;
        if (state.filter === 'completed') return todo.completed;
        return true;
      });

      const handleAddTodo = (e) => {
        e.preventDefault();
        if (newTodoText.trim() === '') return;
        dispatch({ type: 'ADD_TODO', payload: { text: newTodoText } });
        setNewTodoText('');
      };

      return (
        

Görev Yönetim Uygulaması

setNewTodoText(e.target.value)} placeholder="Yeni görev ekle..." />
    {filteredTodos.map((todo) => (
  • dispatch({ type: 'TOGGLE_TODO', payload: { id: todo.id } })} > {todo.text}
  • ))}
); }; ReactDOM.render(, document.getElementById('root'));

Bu örnekte, TodoApp bileşeni, useReducer ile yönetilen görevlerin ve filtre durumunun tek bir yerde tutulduğu merkezi bir state'e sahiptir. Her bir kullanıcı etkileşimi (görev ekleme, tamamlama, silme, filtreleme), ilgili bir action'ın dispatch edilmesiyle tetiklenir. Bu yaklaşım, state mantığını UI mantığından ayırarak, bileşenin daha sade ve odaklanmış olmasını sağlar. Ayrıca, uygulamanın farklı bölümlerinden aynı state mantığına erişim ihtiyacı doğduğunda, useContext ile birleştirilerek global state yönetimi çözümü olarak da kullanılabilir, ki bu da bizi bir sonraki ileri düzey konuya taşıyor.

Gelişmiş useReducer Kullanımı: Middleware, Asenkron İşlemler ve Performans Optimizasyonu

useReducer'ın temel kullanımını ve gerçek dünya senaryolarını gördükten sonra, şimdi daha karmaşık senaryolara, özellikle asenkron işlemlerin yönetimine, middleware entegrasyonuna ve performans optimizasyonlarına odaklanalım. Bu konular, useReducer'ı sadece basit bir state yönetim aracı olmaktan çıkarıp, daha büyük ve ölçeklenebilir uygulamalar için güçlü bir çözüm haline getirir.

Asenkron İşlemlerle useReducer Nasıl Kullanılır?

React'ta state güncellemeleri genellikle senkronize olur. Ancak API çağrıları, zamanlayıcılar veya diğer yan etkiler gibi asenkron işlemler, useReducer'ın "saf" doğasına doğrudan uymaz. Reducer'lar saf olmalı ve yan etkileri içermemelidir. Bu nedenle, asenkron işlemleri yönetmek için action dispatch edilmeden önce veya action dispatch edildikten sonra bazı ara katmanlara ihtiyacımız vardır. Geleneksel olarak Redux ekosisteminde Redux Thunk veya Redux Saga gibi middleware'ler kullanılır. useReducer ile benzer bir pattern'ı kendimiz uygulayabiliriz.

En yaygın yöntem, asenkron bir işlemi başlatan bir "thunk" fonksiyonu oluşturmaktır. Bu fonksiyon, argüman olarak dispatch metodunu alır ve asenkron işlemi tamamladıktan sonra ilgili action'ları dispatch eder. Bu sayede, reducer saf kalır ve sadece state değişimleriyle ilgilenir.


    // Asenkron bir veri çekme işlemi için Action Türleri
    const DATA_FETCH_START = 'DATA_FETCH_START';
    const DATA_FETCH_SUCCESS = 'DATA_FETCH_SUCCESS';
    const DATA_FETCH_ERROR = 'DATA_FETCH_ERROR';

    // Reducer'da yeni durumları ele al
    const dataReducer = (state, action) => {
      switch (action.type) {
        case DATA_FETCH_START:
          return { ...state, loading: true, error: null };
        case DATA_FETCH_SUCCESS:
          return { ...state, loading: false, data: action.payload, error: null };
        case DATA_FETCH_ERROR:
          return { ...state, loading: false, error: action.payload };
        default:
          return state;
      }
    };

    // Asenkron işlemi yöneten thunk benzeri bir fonksiyon
    const fetchData = async (dispatch, url) => {
      dispatch({ type: DATA_FETCH_START });
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const data = await response.json();
        dispatch({ type: DATA_FETCH_SUCCESS, payload: data });
      } catch (error) {
        dispatch({ type: DATA_FETCH_ERROR, payload: error.message });
      }
    };

    const DataFetcher = () => {
      const [state, dispatch] = React.useReducer(dataReducer, {
        loading: false,
        data: null,
        error: null,
      });

      // fetchData fonksiyonunu doğrudan çağırmak yerine, bir sarmalayıcı kullanırız
      const handleFetchClick = () => {
        fetchData(dispatch, 'https://jsonplaceholder.typicode.com/posts/1');
      };

      return (
        

Asenkron Veri Çekme

{state.error &&

Hata: {state.error}

} {state.data &&

Başlık: {state.data.title}

}
); }; ReactDOM.render(, document.getElementById('root'));

Bu yaklaşım, asenkron işlemleri bileşenlerden soyutlayarak, reducer'ın temiz ve saf kalmasını sağlar. fetchData fonksiyonu, işlem başladığında, başarılı olduğunda veya hata oluştuğunda ilgili action'ları dispatch ederek state'i günceller.

Middleware ve useContext ile Global State Yönetimi

Büyük uygulamalarda, state'in birden fazla bileşen tarafından erişilebilir olması gerekebilir. Bu durumda useReduceruseContext ile birleştirmek, Redux'a benzer global state yönetimi çözümü oluşturmanın etkili bir yoludur. Bu sayede, state'i prop drilling yapmadan derinleşimli bileşen ağaçlarında kolayca paylaşabiliriz.


    // Context oluşturma
    const GlobalStateContext = React.createContext();
    const GlobalDispatchContext = React.createContext();

    // Provider bileşeni
    const GlobalStateProvider = ({ children }) => {
      const [state, dispatch] = React.useReducer(dataReducer, {
        loading: false,
        data: null,
        error: null,
      });

      return (
        
          
            {children}
          
        
      );
    };

    // Çocuk bileşeni (state ve dispatch'i kullanır)
    const ChildComponent = () => {
      const state = React.useContext(GlobalStateContext);
      const dispatch = React.useContext(GlobalDispatchContext);

      const handleFetchClick = () => {
        fetchData(dispatch, 'https://jsonplaceholder.typicode.com/todos/1'); // Aynı fetchData fonksiyonunu kullanabiliriz
      };

      return (
        

Çocuk Bileşeni

{state.error &&

Hata: {state.error}

} {state.data &&

Todo Başlığı: {state.data.title}

}
); }; // Ana Uygulama bileşeni const App = () => (

Global State Uygulaması

); ReactDOM.render(, document.getElementById('root'));

Bu yapı, uygulamanızdaki herhangi bir bileşenin GlobalStateContext'ten state'e ve GlobalDispatchContext'ten dispatch fonksiyonuna erişmesini sağlar. Böylece, bileşenleriniz temiz kalır ve global state'i yönetmek için ayrı bir kütüphaneye ihtiyaç duymazsınız.

Performans Optimizasyonu

useReducer, useState'e kıyasla bazı performans avantajları sunabilir, özellikle çok sık güncellenen veya karmaşık state yapılarına sahip uygulamalarda. Ancak yine de dikkatli olmak gerekir. Reducer her çağrıldığında yeni bir state nesnesi döndürdüğü için, React bu yeni state'i karşılaştırarak ilgili bileşenleri yeniden render eder. Aşırı yeniden render'ları önlemek için:

  • Memoizasyon (React.memo, useCallback, useMemo): Alt bileşenlerinizi React.memo ile sarmalayarak, sadece prop'ları değiştiğinde yeniden render olmalarını sağlayabilirsiniz. dispatch fonksiyonu referans olarak sabit olduğu için, bu optimizasyonu yaparken sorun yaşamazsınız. Ancak reducer'ın döndürdüğü state'in bazı kısımları değişmese bile, tüm state objesi değişiyorsa, bileşenleriniz yine de yeniden render olabilir. Bu durumda, useSelector benzeri özel hook'lar yazarak sadece ihtiyaç duyulan state parçalarını çekebilirsiniz.
  • Reducer'ı Dışarıda Tanımlamak: Reducer fonksiyonunu bileşen dışında tanımlamak, her render'da yeniden oluşturulmasını engeller ve dolayısıyla performansı artırır.
  • Başlangıç State'ini Lazy Yüklemek: Eğer başlangıç state'i pahalı bir hesaplama gerektiriyorsa, useReducer'ın üçüncü argümanı olan init fonksiyonunu kullanarak bu hesaplamayı sadece ilk render'da yapabilirsiniz.
    
            const init = (initialCount) => ({ count: initialCount });
            const [state, dispatch] = React.useReducer(reducer, 0, init); // 0, initialCount'a dönüşür
          

Uzman İpucu: Büyük ve karmaşık state ağaçlarında, reducer'ları mantıksal olarak daha küçük parçalara (örneğin, combineReducers mantığıyla) bölmek, her bir reducer'ın yalnızca kendi ilgilendiği state kısmını yönetmesini sağlar. Bu, hem kodu daha modüler hale getirir hem de olası performans sorunlarını azaltır, çünkü bir action yalnızca belirli bir state parçasını etkilerken diğer reducer'lar daha az hesaplama yapar.

Bu ileri düzey teknikler, useReducer'ı uygulamanızın omurgası haline getirme potansiyeline sahiptir ve büyük projelerde karşılaşılan zorlukların üstesinden gelmenize yardımcı olur.

useReducer ile Test Edilebilirlik ve Bakım Kolaylığı Nasıl Sağlanır?

Yazılım geliştirmenin önemli bir boyutu, yazdığımız kodun sadece çalışır olması değil, aynı zamanda kolayca test edilebilir ve uzun vadede bakımı yapılabilir olmasıdır. useReducer, bu iki konuda useState'e kıyasla belirgin avantajlar sunar, özellikle uygulamanızın karmaşıklığı arttıkça bu avantajlar daha da önem kazanır.

Test Edilebilirlik

useReducer'ın temelindeki reducer fonksiyonları, saf fonksiyonlar olma prensibine dayanır. Saf fonksiyonlar, belirli input'lar (mevcut state ve action) verildiğinde her zaman aynı output'u (yeni state) döndürürler ve dış dünya ile hiçbir yan etkiye sahip değillerdir (API çağrıları, konsol logları vb. gibi). Bu özellik, reducer'ları test etmeyi inanılmaz derecede kolaylaştırır. Bir reducer'ı test etmek için, ona belirli bir başlangıç state'i ve bir action nesnesi verir, ardından beklenen yeni state ile dönen state'i karşılaştırırsınız. Bu işlem, herhangi bir UI bileşenini render etmeye veya bir tarayıcı ortamı oluşturmaya gerek kalmadan, tamamen izole bir şekilde yapılabilir.


    // Daha önceki todosReducer fonksiyonumuz
    const todosReducer = (state, action) => {
      switch (action.type) {
        case 'ADD_TODO':
          return {
            ...state,
            todos: [
              ...state.todos,
              {
                id: state.nextId,
                text: action.payload.text,
                completed: false,
              },
            ],
            nextId: state.nextId + 1,
          };
        case 'TOGGLE_TODO':
          return {
            ...state,
            todos: state.todos.map((todo) =>
              todo.id === action.payload.id
                ? { ...todo, completed: !todo.completed }
                : todo
            ),
          };
        case 'REMOVE_TODO':
          return {
            ...state,
            todos: state.todos.filter((todo) => todo.id !== action.payload.id),
          };
        default:
          return state;
      }
    };

    // Reducer için örnek bir test senaryosu (örneğin Jest ile)
    // describe('todosReducer', () => {
    //   test('should add a todo', () => {
    //     const initialState = { todos: [], nextId: 1 };
    //     const action = { type: 'ADD_TODO', payload: { text: 'Test Todo' } };
    //     const newState = todosReducer(initialState, action);
    //     expect(newState.todos.length).toBe(1);
    //     expect(newState.todos[0].text).toBe('Test Todo');
    //     expect(newState.nextId).toBe(2);
    //   });

    //   test('should toggle a todo', () => {
    //     const initialState = { todos: [{ id: 1, text: 'Test Todo', completed: false }], nextId: 2 };
    //     const action = { type: 'TOGGLE_TODO', payload: { id: 1 } };
    //     const newState = todosReducer(initialState, action);
    //     expect(newState.todos[0].completed).toBe(true);
    //   });

    //   test('should remove a todo', () => {
    //     const initialState = { todos: [{ id: 1, text: 'Test Todo', completed: false }], nextId: 2 };
    //     const action = { type: 'REMOVE_TODO', payload: { id: 1 } };
    //     const newState = todosReducer(initialState, action);
    //     expect(newState.todos.length).toBe(0);
    //   });
    // });
  

Bu testler, reducer'ın beklenen davranışları sergileyip sergilemediğini hızlı ve güvenilir bir şekilde kontrol etmemizi sağlar. useState ile karşılaştırıldığında, useState'in güncelleyici fonksiyonları doğrudan bir bileşenin içinde yer aldığı için, bunları izole bir şekilde test etmek daha zordur ve genellikle UI render etmeyi gerektirir.

Bakım Kolaylığı

Uygulamalar büyüdükçe, state yönetim mantığının da karmaşıklığı artar. useReducer, bu karmaşıklığı yönetmek için harika bir yapı sağlar:

  1. Merkezi State Mantığı: Tüm state değişim mantığı tek bir yerde (reducer fonksiyonunda) bulunur. Bu, bir bug'ı izlemek veya yeni bir özellik eklemek istediğinizde, state'in nasıl değiştiğini anlamak için farklı dosyalara veya bileşenlere bakmak yerine doğrudan reducer'a odaklanabileceğiniz anlamına gelir.
  2. Action'lar ile Açık İletişim: Action nesneleri, uygulamanızda neyin gerçekleştiğini açıkça belirtir. Bir action'ın type özelliği, state'i neyin değiştirdiğini hemen anlamanızı sağlar. Bu açık iletişim, hem yeni geliştiricilerin projeye adaptasyonunu kolaylaştırır hem de mevcut ekibin farklı bileşenler arasındaki etkileşimleri daha iyi anlamasına yardımcı olur.
  3. Modülerlik ve Yeniden Kullanılabilirlik: Reducer fonksiyonlarını ve ilgili action türlerini ayrı dosyalarda tanımlayarak, state mantığınızı modüler hale getirebilirsiniz. Bu reducer'lar, farklı bileşenler veya hatta farklı uygulamalar arasında kolayca yeniden kullanılabilir. Örneğin, bir kullanıcı kimlik doğrulama reducer'ı, uygulamanızın farklı bölümlerinde veya diğer projelerde de işe yarayabilir.
  4. Geliştirici Araçları Desteği: Redux DevTools gibi araçlar, useReducer ile birlikte kullanıldığında (küçük bir adaptasyonla), dispatch edilen her action'ı ve her state değişimini görselleştirmeyi sağlar. Bu, hata ayıklama sürecini inanılmaz derecede hızlandırır ve state'in zaman içindeki akışını anlamayı kolaylaştırır.

Sonuç olarak, useReducer sadece karmaşık state'leri yönetmekle kalmaz, aynı zamanda uygulamanızın genel kalitesini ve geliştirici deneyimini de iyileştirir. Kodunuz daha öngörülebilir, daha kolay test edilebilir ve dolayısıyla uzun vadede bakımı çok daha kolay hale gelir. Bu, özellikle sürekli gelişen ve büyüyen büyük ölçekli uygulamalar için kritik bir avantajdır.

Mobil Uyumlu Tasarımlarda State Yönetimi ve useReducer

Modern web uygulamaları, sadece masaüstü bilgisayarlarda değil, aynı zamanda tabletler ve akıllı telefonlar gibi çeşitli mobil cihazlarda da sorunsuz bir deneyim sunmalıdır. Bu durum, mobil uyumlu (responsive) tasarımların önemini artırmaktadır. Peki, useReducer gibi bir state yönetim aracı, mobil uyumlu tasarımlarla nasıl bir ilişki içindedir ve bu iki konuyu bir araya getirerek daha güçlü uygulamalar nasıl inşa edebiliriz?

useReducer doğrudan CSS veya HTML layout ile ilgili bir araç olmasa da, uygulamanın UI durumunu (örneğin, bir menünün açık/kapalı olması, bir öğenin görünürlüğü, farklı ekran boyutlarına göre değişen veri yapıları) yöneterek responsive tasarımın dinamiklerini destekleyebilir. Örneğin, bir mobil görünümde bir navigasyon menüsünün kapalı başlaması, ancak bir butona tıklanınca açılması gibi durumlar, useReducer ile yönetilebilecek tipik senaryolardır. Kullanıcının hangi cihaz türünde olduğuna veya ekran boyutuna göre farklı bileşenlerin veya veri setlerinin gösterilmesi gerektiğinde, bu bilgiyi state'e kaydedip useReducer ile yönetmek, UI'ın tutarlı ve öngörülebilir bir şekilde davranmasını sağlar.

Aşağıda, responsive bir navigasyon menüsünün state yönetimini useReducer ile nasıl yapabileceğimize dair basit bir örnek verilmiştir. Bu örnekte, menünün mobil görünümde nasıl açılıp kapanacağını state ile yönetiyoruz. Ardından, mobil uyumlu HTML/CSS için genel bir media query örneği sunacağız, bu useReducer ile yönettiğimiz UI durumunu görsel olarak nasıl etkileyebileceğimizi gösteriyor.


    // Menü State Yönetimi için Reducer
    const menuReducer = (state, action) => {
      switch (action.type) {
        case 'TOGGLE_MENU':
          return { ...state, isOpen: !state.isOpen };
        case 'CLOSE_MENU':
          return { ...state, isOpen: false };
        case 'OPEN_MENU':
          return { ...state, isOpen: true };
        default:
          return state;
      }
    };

    const ResponsiveNav = () => {
      const [state, dispatch] = React.useReducer(menuReducer, { isOpen: false });

      // Ekran boyutu değişimini dinlemek için bir hook kullanılabilir (örneğin, useWindowSize)
      // Bu örnekte, basitlik adına sadece bir toggle butonu ile menüyü kontrol ediyoruz.
      // Gerçek bir responsive uygulamada, CSS media query'lerine ek olarak
      // JavaScript ile viewport genişliğini takip edip, belirli bir eşiğin altına düşüldüğünde
      // dispatch({ type: 'CLOSE_MENU' }) gibi bir action gönderebiliriz.

      return (
        }>
            
  • Anasayfa
  • Hizmetler
  • Hakkımızda
  • İletişim
  • ); }; // Bu HTML dosyasının kısmına eklenebilecek stil örnekleri: // ReactDOM.render(, document.getElementById('root'));

    Yukarıdaki örnekte, ResponsiveNav bileşeni, menünün açık/kapalı durumunu useReducer ile yönetir. CSS media query'leri, belirli bir ekran genişliğinin altında menü düğmesini gösterirken, bağlantı listesini gizler ve dikey olarak düzenler. Menü düğmesine tıklandığında, dispatch({ type: 'TOGGLE_MENU' }) action'ı ile state güncellenir ve .nav-links.open sınıfı eklenerek menü görünür hale gelir. Bu yaklaşım, state yönetimini UI'ın görsel değişimlerinden ayırarak, daha temiz ve sürdürülebilir bir kod tabanı oluşturmaya yardımcı olur. Ayrıca, bu state bilgisi, diğer bileşenlerin (örneğin, menü açıldığında ana içeriğin kaydırılmasını engelleyen bir overlay) davranışını da etkileyebilir.

    Sonuç ve Sıkça Sorulan Sorular

    React'ın useReducer hook'u, sadece quiz sorularında karşımıza çıkan basit bir kavram olmanın ötesinde, özellikle orta ve büyük ölçekli uygulamalarda karmaşık state yönetimi sorunlarına zarif ve güçlü bir çözüm sunar. useState'in yetersiz kaldığı durumlarda, merkezi state mantığı, test edilebilirlik ve bakım kolaylığı gibi avantajlarıyla öne çıkar. Bu makalede, useReducer'ın temellerinden başlayarak, görev yönetim uygulaması gibi gerçek dünya senaryolarına, asenkron işlemlerin yönetimine, performans optimizasyonlarına ve hatta mobil uyumlu tasarımlarla entegrasyonuna kadar geniş bir yelpazeyi ele aldık. Unutmayın ki, useReduceruseContext ile birleştirmek, Redux benzeri bir global state yönetim sistemi kurmanıza olanak tanıyarak uygulamanızın ölçeklenebilirliğini artırır. Kodunuzu daha düzenli, öngörülebilir ve sürdürülebilir hale getirmek için useReducer'ı araç kutunuza eklemek, sizi kesinlikle daha yetkin bir React geliştiricisi yapacaktır.

    Sıkça Sorulan Sorular (SSS)

    1. useReducer ne zaman useState yerine tercih edilmelidir?

    useReducer, state mantığı karmaşık olduğunda, birden fazla alt state'in birbiriyle ilişkili olduğu durumlarda, state güncellemelerinin önceki state'e bağlı olduğu senaryolarda veya global state yönetimi için useContext ile birleştirilmesi gerektiğinde useState yerine tercih edilmelidir. Özellikle, state güncellemeleri belirli bir sırayı takip ediyorsa veya birden fazla farklı action türü aynı state'i etkiliyorsa useReducer daha temiz bir çözüm sunar.

    2. Redux ile useReducer arasındaki temel farklar nelerdir?

    Hem Redux hem de useReducer, Reducer prensibine dayanır. Ancak Redux, global state yönetimi için daha kapsamlı bir ekosistem (middleware'ler, geliştirici araçları, kütüphane entegrasyonları) sunarken, useReducer React'ın kendi içinde gelen basit bir hook'tur. useReducer, useContext ile birleştirildiğinde Redux'a benzer bir yapı oluşturabilir ancak Redux'un sunduğu bazı gelişmiş özelliklerden (örn. kolay time-travel debugging) yoksundur. Genellikle, Redux'un getirdiği ek yükten kaçınmak isteniyorsa veya uygulamanın state yönetimi ihtiyaçları Redux kadar karmaşık değilse useReducer tercih edilebilir.

    3. useReducer ile asenkron işlemleri (API çağrıları gibi) nasıl yönetebilirim?

    Reducer'lar saf fonksiyonlar olmalı ve yan etkileri (side effects) içermemelidir. Bu nedenle, asenkron işlemleri doğrudan reducer içinde yapamayız. Bunun yerine, dispatch fonksiyonunu alan "thunk" benzeri ara katman fonksiyonları oluşturarak asenkron işlemleri yönetiriz. Bu fonksiyonlar, asenkron işlemin farklı aşamalarında (başlama, başarılı olma, hata) ilgili action'ları dispatch ederek reducer'ın state'i güncellemesini sağlar.

    4. useReducer performans sorunlarına yol açar mı? Nasıl optimize edebilirim?

    Doğru kullanılmadığında her hook gibi useReducer da performansı etkileyebilir. Özellikle her state değişiminde tüm component ağacının gereksiz yere render edilmesinden kaçınmak önemlidir. Optimizasyon için şu yöntemleri kullanabilirsiniz: reducer fonksiyonunu bileşen dışında tanımlamak, başlangıç state'ini lazy yüklemek (üçüncü argüman olarak init fonksiyonu ile), alt bileşenleri React.memo ile memoize etmek ve useCallback veya useMemo kullanarak pahalı hesaplamaları ve fonksiyon referanslarını önbelleğe almak.

    5. Birden fazla reducer'ı nasıl birleştirebilirim?

    Redux'taki combineReducers mantığına benzer şekilde, useReducer ile de birden fazla reducer'ı birleştirebilirsiniz. Bunun için, ana bir reducer oluşturup bu reducer içinde alt reducer'ları çağırarak her birinin kendi ilgili state parçasını yönetmesini sağlayabilirsiniz. Örneğin, todosReducer ve authReducer gibi farklı reducer'larınız varsa, ana reducer, action'ı her ikisine de gönderip döndürülen yeni state parçalarını birleştirerek ana state'i oluşturabilir.

    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