Yazılarımız

OfisData

STATE YÖNETİMİ STRATEJİSİ SEÇMEK VE UYGULAMAK

React projesi büyüdükçe en çok tartışılan konu genellikle şuna döner: “State’i nereye koyacağız?” Bir form alanı, bir filtre, bir sepet, bir kullanıcı oturumu… Hepsini aynı yere yığmak hem hatayı artırır hem de ekip içinde geliştirme hızını düşürür. Tam tersi şekilde her şeyi parçalara bölmek de yönetimi zorlaştırabilir.

İyi bir state yönetimi stratejisi, tek bir araç seçmekten çok daha fazlasıdır. Amaç; veri akışını anlaşılır kılmak, gereksiz güncellemeleri azaltmak ve değişiklikleri güvenle yapmak. Bu rehberde, React’in yerleşik araçlarını (useState, useReducer, context) doğru konumlandırmayı; ihtiyaç varsa store yaklaşımını mantıklı bir çerçevede ele almayı anlatacağız.

Uygulamalı örnekler, kod kalıpları ve ekip içinde ortak dil oluşturma adımları için react eğitimi içeriğine de göz atabilirsiniz.

Masaüstünde React bileşen ağacını gösteren diyagram ve yanında state akışı notları bulunan planlama masası

State kapsamını doğru belirlemek ve sınır çizmek

Stratejinin başlangıç noktası, state’in kapsadığı alanı netleştirmektir. “Bu veri sadece bu bileşeni mi ilgilendiriyor, yoksa sayfanın farklı yerleri de mi kullanıyor?” sorusu, ilk ayrımı yapmayı sağlar. Çoğu durumda çözüm, state’i en yakın ihtiyaç noktasına koymak ve yalnızca gerektiğinde yukarı taşımaktır.

Burada iki yaygın yanılgı vardır: (1) “Her şeyi en üstte tutarsak kolay olur” yaklaşımı; (2) “Her şeyi local tutarsak performans kazanırız” yaklaşımı. İkisi de tek başına doğru değildir. Ölçüt, değişimin kimleri etkilediği ve iş kuralının nerede yaşadığı olmalıdır.

Local state kullanarak hızlı ilerlemek ve karmaşayı azaltmak

Input değerleri, aç/kapa durumları, geçici UI tercihleri gibi veriler için local state genellikle en doğru çözümdür. Bu veriler sayfa yenilenince zaten sıfırlanabilir ve başka bileşenler tarafından kullanılmaz. Local state, bağımlılığı azaltır ve değişikliği daha izole hale getirir.

Lifting state up kararını veriye göre almak ve yönetmek

Aynı veri birden fazla kardeş bileşeni etkiliyorsa state’i ortak parent’a taşımak gerekir. Ancak bu taşıma, “parent her render’da her şeyi hareketlendirmesin” diye bileşen sınırlarını iyi kurmayı da gerektirir. Bu noktada memoization, prop tasarımı ve bileşen bölme gibi tekniklerle birlikte düşünmek önemlidir.


Derived state yaklaşımını doğru uygulamak ve sadeleştirmek

Derived state, başka bir state’ten hesaplanan veridir. Örneğin “filtrelenmiş liste” çoğu zaman “liste + filtre” ile hesaplanabilir. Bunu ayrıca state’te tutmak; senkronizasyon hataları, gereksiz güncellemeler ve test zorluğu getirir. Sade bir yaklaşım, derived veriyi render sırasında üretmek ve gerektiğinde useMemo ile optimize etmektir.

Kaynak state’i tek tutup hesaplamayı kontrollü yapmak

Tek “source of truth” tutmak, hata ayıklamayı kolaylaştırır. “Neden liste yanlış çıktı?” sorusu tek bir kaynağa iner. Hesaplama pahalıysa memoization ile korunur; ama state’i çoğaltarak yönetim yükü artırılmaz.

useMemo ile hesaplamayı sınırlamak ve stabil tutmak

useMemo, her şeyi hızlandırmak için değil, pahalı işlemleri gereksiz tekrar etmemek için kullanılır. Burada dependency listesini doğru kurmak gerekir. Aksi halde hesaplamayı yanlış zamanda sabitlemek, beklenmedik sonuçlara yol açabilir.

import React, { useMemo, useState } from "react";

export default function Products({ items }) {
  const [query, setQuery] = useState("");
  const [onlyInStock, setOnlyInStock] = useState(false);

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    return items
      .filter(x => (onlyInStock ? x.stock > 0 : true))
      .filter(x => (q ? x.title.toLowerCase().includes(q) : true));
  }, [items, query, onlyInStock]);

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <label>
        <input
          type="checkbox"
          checked={onlyInStock}
          onChange={(e) => setOnlyInStock(e.target.checked)}
        />
        Stokta olanlar
      </label>
      <div>Sonuç: {filtered.length}</div>
    </div>
  );
}

useReducer ile iş kurallarını merkezileştirmek ve test etmek

State karmaşıklaştığında (çoklu alan, çoklu aksiyon, doğrulama, adım adım akış) useState zincirleri hızla dağınık hale gelir. Bu tür durumlarda useReducer, güncellemeleri tek bir “aksiyon” sözlüğüne toplar ve davranışı netleştirir. Ayrıca reducer mantığı, UI’dan daha bağımsız test edilebilir.

Action tabanlı düşünerek değişimi izlemek ve yönetmek

Reducer, “ne oldu?” sorusuna cevap veren aksiyonlar üzerinden ilerler. Bu yaklaşım, ekip içinde ortak dil kurmayı sağlar. Örneğin “ADD_ITEM”, “REMOVE_ITEM”, “APPLY_COUPON” gibi aksiyonlar, hem loglamayı hem de hata ayıklamayı kolaylaştırır.

Reducer yapısını küçük tutmak ve büyütmeyi planlamak

Reducer’ı tek bir dev fonksiyona çevirmek yerine, alanlara göre bölmek ve ortak yardımcılarla ilerlemek önemlidir. Böylece yeni özellik eklemek, mevcut davranışı kırmadan yapılabilir. Ayrıca side effect’leri (API çağrıları gibi) reducer’ın dışına taşımak daha sağlıklıdır.

import React, { useReducer } from "react";

const initial = { items: [], coupon: null, note: "" };

function cartReducer(state, action) {
  switch (action.type) {
    case "ADD_ITEM":
      return { ...state, items: [...state.items, action.payload] };
    case "REMOVE_ITEM":
      return { ...state, items: state.items.filter(x => x.id !== action.payload) };
    case "APPLY_COUPON":
      return { ...state, coupon: action.payload };
    case "SET_NOTE":
      return { ...state, note: action.payload };
    default:
      return state;
  }
}

export default function Cart() {
  const [state, dispatch] = useReducer(cartReducer, initial);

  return (
    <div>
      <button onClick={() => dispatch({ type: "ADD_ITEM", payload: { id: 1, title: "Tişört" } })}>
        Ürün ekle
      </button>
      <input
        placeholder="Sipariş notu"
        value={state.note}
        onChange={(e) => dispatch({ type: "SET_NOTE", payload: e.target.value })}
      />
      <div>Sepet: {state.items.length} ürün</div>
    </div>
  );
}

Context kullanımını doğru sınırlandırmak ve performansı korumak

Context, “prop drilling” sorununu azaltır; ama yanlış kullanılırsa geniş kapsamlı yeniden render dalgaları yaratabilir. Bu yüzden context’i “her şeyin deposu” gibi görmek yerine, belirli alanlar için net sınırlarla kullanmak gerekir.

Context’i bölerek güncellemeyi daraltmak ve izole etmek

Tek bir provider altında kullanıcı, tema, sepet ve izinler birleşirse; küçük bir değişim birçok tüketiciyi etkileyebilir. Daha iyi bir yaklaşım, context’i alanlara ayırmak (ör. AuthContext, ThemeContext, CartContext) ve provider’ları ihtiyaca göre bileşen ağacında doğru yerde konumlandırmaktır.

Provider value referansını stabilize etmek ve kontrol etmek

Context value her render’da yeni obje olursa, tüketiciler tekrar çalışabilir. Provider value’sunu useMemo ile sabitlemek, gereksiz güncellemeleri azaltabilir. Burada hedef; gerçek değişim olduğunda güncellemek, diğer durumlarda stabil kalmaktır.

Ekranda birden fazla context provider katmanı ve her birinin kapsadığı bileşen alanlarını gösteren şema

Global store kararını doğru vermek ve entegrasyon planlamak

Bazı projelerde, birden fazla sayfa arasında paylaşılan karmaşık veri akışı vardır: oturum, yetkiler, feature flag’ler, sepet, favoriler, offline kuyruk gibi. Bu noktada store yaklaşımı (Redux, Zustand vb.) gündeme gelebilir. Buradaki kritik nokta, “store kullanmak” değil, hangi problem için kullanmaktır.

Hangi veri global olmalı sorusunu netleştirmek ve sınırlandırmak

Global veri; sayfalar arası kalıcılık, birden çok bağımsız alanın aynı veriye ihtiyaç duyması veya olay bazlı güncellemeler gibi ihtiyaçlar doğurduğunda anlam kazanır. Aksi halde her şeyi store’a taşımak, bileşenleri gereksiz bağımlı hale getirir. İyi bir strateji, globali küçük tutup lokal alanları korumaktır.

Store entegrasyonunu adımlı yapmak ve risk azaltmak

Yeni bir store’u bir günde tüm projeye yaymak yerine, önce tek bir alan (ör. sepet) seçip standardı orada oturtmak daha güvenli olur. Aksiyon isimlendirmesi, selector yazımı, hata loglama ve devtool kullanımı netleşince ölçeklemek kolaylaşır. Bu yaklaşım, ekip içinde ortak davranış geliştirmeyi de hızlandırır.


Strateji dokümantasyonunu oluşturmak ve sürdürülebilir yapmak

State yönetimi, yalnızca kod değil aynı zamanda ekip içi anlaşmadır. Herkes farklı bir yaklaşım benimsediğinde, proje ilerledikçe tutarlılık kaybolur. Bu yüzden temel kararları kısa bir rehberde toparlamak, uzun vadede maliyeti düşürür.

Ortak karar setini maddelerle belirlemek ve paylaşmak

Aşağıdaki kontrol listesi, “hangi aracı ne zaman kullanıyoruz?” sorusuna hızlı yanıt verir. Listeyi proje ihtiyaçlarına göre uyarlayıp sürümlemek, ekip içinde kalıcı bir standarda dönüşmesini sağlar.

  1. UI geçici verileri local state ile taşımak
  2. Sayfa içi paylaşım için lifting state up uygulamak
  3. Karmaşık akış için useReducer ile aksiyon tanımlamak
  4. Geniş paylaşım için context’i bölerek kullanmak
  5. Çok sayfalı kalıcılık için store ihtiyacını gerekçelendirmek
  6. Hesaplanan verileri derived state yerine memoize etmek

Performans ve hata ayıklamayı rutine eklemek ve iyileştirmek

Stratejinin işe yaradığını anlamanın yolu ölçmektir. Profiler ile render dalgalarını görmek, reducer aksiyonlarını loglamak ve context güncellemelerini izlemek, hata ayıklamayı hızlandırır. Böylece “seçtiğimiz yaklaşım ölçekliyor mu?” sorusuna veriye dayalı cevap üretmek mümkün olur.

Toplantı masasında state stratejisi karar maddeleri yazan doküman ve yanında açık bilgisayarda kod inceleme ekranı

Kapanış: Sağlam bir state yönetimi stratejisi; local state’i doğru kullanmak, derived state’i sade tutmak, karmaşık iş kurallarını reducer ile toplamak, context’i sınırlandırmak ve gerekirse store’u hedefli devreye almakla oluşur. Bu kararları ekip içinde ortak dile çevirip dokümante etmek de sürdürülebilirliği artırır. Daha fazla pratik ve örnek senaryo için react eğitimi sayfasını inceleyebilirsiniz.

 Vimaj