İçeriğe geç
react

Zustand ile Modern State Yönetimi

Tarık Tunç
Zustand ile Modern State Yönetimi

Zustand ile Modern State Yönetimi

Zustand, React ekosistemindeki en hafif ve kullanımı en kolay state management kütüphanelerinden biridir. Sadece ~1.5 KB bundle boyutuyla, Redux'un boilerplate karmaşıklığından kaçınırken güçlü bir state yönetimi sunar. Bu rehberde, Zustand'ın temellerinden ileri düzey middleware kullanımına kadar kapsamlı bir yolculuk yapacağız.

Neden Zustand?

Zustand, Poimandres (pmndrs) topluluğu tarafından geliştirilmiş, minimalist bir state yönetim kütüphanesidir. Provider gerekmez, boilerplate kodu neredeyse sıfırdır ve selector bazlı yapısı sayesinde otomatik render optimizasyonu sağlar. Ayrıca React dışında (vanilla JS) da kullanılabilir.

Temel Kullanım

import { create } from 'zustand';

// Basit bir store
interface CounterStore {
  count: number;
  increment: () => void;
  decrement: () => void;
  incrementBy: (amount: number) => void;
  reset: () => void;
}

const useCounterStore = create<CounterStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  incrementBy: (amount) => set((state) => ({ count: state.count + amount })),
  reset: () => set({ count: 0 }),
}));

// Kullanım — Provider gerekmez!
function Counter() {
  const count = useCounterStore((state) => state.count);
  const increment = useCounterStore((state) => state.increment);
  const decrement = useCounterStore((state) => state.decrement);

  return (
    <div>
      <h2>Sayaç: {count}</h2>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </div>
  );
}

// Selector ile sadece ihtiyacınız olan state'i alın
// count değiştiğinde sadece bu component render olur
function CountDisplay() {
  const count = useCounterStore((state) => state.count);
  return <span>{count}</span>;
}

// Action'lar değişmez, gereksiz render olmaz
function CountButtons() {
  const { increment, decrement, reset } = useCounterStore(
    (state) => ({ increment: state.increment, decrement: state.decrement, reset: state.reset })
  );

  return (
    <div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Sıfırla</button>
    </div>
  );
}
İpucu: Zustand store'larını component dışında da kullanabilirsiniz: useCounterStore.getState().increment(). Bu özellik, API middleware'leri veya utility fonksiyonlarında çok işe yarar.

Gerçek Dünya Store: E-Ticaret Sepet

import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
  image: string;
}

interface CartStore {
  items: CartItem[];
  isOpen: boolean;

  // Actions
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (id: string) => void;
  updateQuantity: (id: string, quantity: number) => void;
  clearCart: () => void;
  toggleCart: () => void;

  // Computed
  totalItems: () => number;
  totalPrice: () => number;
}

const useCartStore = create<CartStore>()(
  devtools(
    persist(
      subscribeWithSelector(
        immer((set, get) => ({
          items: [],
          isOpen: false,

          addItem: (item) =>
            set((state) => {
              const existing = state.items.find((i) => i.id === item.id);
              if (existing) {
                existing.quantity += 1;
              } else {
                state.items.push({ ...item, quantity: 1 });
              }
            }),

          removeItem: (id) =>
            set((state) => {
              state.items = state.items.filter((i) => i.id !== id);
            }),

          updateQuantity: (id, quantity) =>
            set((state) => {
              const item = state.items.find((i) => i.id === id);
              if (item) {
                if (quantity <= 0) {
                  state.items = state.items.filter((i) => i.id !== id);
                } else {
                  item.quantity = quantity;
                }
              }
            }),

          clearCart: () => set({ items: [] }),
          toggleCart: () => set((state) => ({ isOpen: !state.isOpen })),

          totalItems: () => get().items.reduce((sum, item) => sum + item.quantity, 0),
          totalPrice: () =>
            get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
        }))
      ),
      {
        name: 'cart-storage',
        partialize: (state) => ({ items: state.items }),
      }
    ),
    { name: 'CartStore' }
  )
);

// Sepet ikonunda kullanım
function CartIcon() {
  const totalItems = useCartStore((state) => state.totalItems());
  const toggleCart = useCartStore((state) => state.toggleCart);

  return (
    <button onClick={toggleCart} className="relative">
      🛒
      {totalItems > 0 && (
        <span className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-5 h-5 text-xs flex items-center justify-center">
          {totalItems}
        </span>
      )}
    </button>
  );
}

Middleware Kullanımı

Zustand'ın middleware sistemi, store'unuza ekstra özellikler eklemenizi sağlar. En yaygın middleware'ler şunlardır:

Middleware Açıklama
devtools Redux DevTools entegrasyonu
persist State'i localStorage/sessionStorage'a kaydetme
immer Mutable syntax ile immutable state güncellemesi
subscribeWithSelector Store değişikliklerini selector bazlı dinleme

Store Slices Pattern

import { create, StateCreator } from 'zustand';

// Her slice kendi state ve action'larını tanımlar
interface UserSlice {
  user: { name: string; email: string } | null;
  setUser: (user: UserSlice['user']) => void;
  clearUser: () => void;
}

interface SettingsSlice {
  theme: 'light' | 'dark';
  language: 'tr' | 'en';
  toggleTheme: () => void;
  setLanguage: (lang: 'tr' | 'en') => void;
}

type CombinedStore = UserSlice & SettingsSlice;

const createUserSlice: StateCreator<CombinedStore, [], [], UserSlice> = (set) => ({
  user: null,
  setUser: (user) => set({ user }),
  clearUser: () => set({ user: null }),
});

const createSettingsSlice: StateCreator<CombinedStore, [], [], SettingsSlice> = (set) => ({
  theme: 'light',
  language: 'tr',
  toggleTheme: () => set((state) => ({ theme: state.theme === 'light' ? 'dark' : 'light' })),
  setLanguage: (language) => set({ language }),
});

// Slice'ları birleştir
const useAppStore = create<CombinedStore>()((...args) => ({
  ...createUserSlice(...args),
  ...createSettingsSlice(...args),
}));
Uyarı: Zustand selector'larında object dönerken dikkatli olun. useStore((s) => ({ a: s.a, b: s.b })) her render'da yeni object oluşturur. Bunun yerine useShallow kullanın: useStore(useShallow((s) => ({ a: s.a, b: s.b }))).
Bilgi: Zustand store'ları singleton'dır ve React component ağacından bağımsızdır. Bu, test yazarken store'u kolayca resetlemeniz gerektiği anlamına gelir. Her test öncesi useStore.setState(initialState) çağırın.

Sonuç

Zustand, minimal API'si ve güçlü middleware ekosistemiyle React state yönetiminde mükemmel bir seçenektir. Provider gerektirmemesi, selector bazlı optimizasyonu ve TypeScript desteğiyle hem küçük hem de büyük projelerde verimli çalışır. Immer ile mutable syntax, persist ile kalıcılık ve devtools ile debug kolaylığı sağlar. Eğer Redux'un karmaşıklığından bıktıysanız veya Context API'nin performans sınırlamalarıyla karşılaşıyorsanız, Zustand tam size göre.

Kaynaklar

İlgili Yazılar